/*
 * @package autofillForms
 * @author Sebastian Tschan
 * @copyright (c) Sebastian Tschan
 * @license GNU General Public License
 * @link https://blueimp.net/mozilla/
 */

var autofillForms = {
	
	// The selected profile index:
	profileIndex: null,
	// The selected global profile index:
	globalProfileIndex: null,
	// The selected form fields context menu profile index
	formFieldsContextMenuProfileIndex: null,
	// The list of profile labels:
	profileLabels: null,
	// The list of profile site rules:
	profileSiteRules: null,
	// The list of form field rules:
	fieldRules: null,
	// The tree representing the field rules list:
	tree: null,
	// The tree view:
	treeView: null,
	// The tree view frontend:
	treeBox: null,
	// Holds the selection object for the treeview:
	selection: null,
	// Remembers the last selected index of the treeview:
	lastSelectedIndex: null,
	// Determines if sort is to be ascending or descending:
	ascending: null,
	// The profiles listBox:
	profilesTree: null,
	// The profiles tree view:
	profilesTreeView: null,
	// The profiles tree view frontend:
	profilesTreeBox: null,
	// Holds the selection object for the profiles treeview:
	profilesSelection: null,
	// The profiles sort order:
	profilesAscending: null,
	// Autofill forms preferences branch:
	autofillFormsPrefs: null,
	// Object containing the shortcuts information (modifiers, key or keycode):
	shortcut: null,
	// Object containing the mouse button shortcuts information:
	mouseButton: null,
	// Helper var to do the opposite of the current setting:
	invertedSetting: null,
	// Array containing the rule element types ("begins with", "contains", ...):
	ruleElementTypes: null,
	// Containes the reference to the current rule field:
	currentRuleField: null,
	// Defines the index selected for the last alternative fieldRules selection:
	fieldRuleAlternativesIndex: null,
	// Stores the length of the last created list of alternative fieldRules:
	fieldRuleAlternativesLength: null,
	// Hash to store lists of alternatives (used for radio input fields):
	fieldRuleAlternativesHash: null,
	// Cache to reuse/clone fieldRuleAlternatives on the same form fill run:
	fieldRuleAlternativesCache: null,
	// Array of dynamic tags:
	dynamicTags: null,
	// Array of dynamic tag codes, associated to the dynamic tags:
	dynamicTagCodes: null,
	// Determines if a textbox is focused on the rule editor:
	ruleEditorTextBoxFocused: null,
	// Determines if a textbox is focused on the tag editor:
	tagEditorTextBoxFocused: null,
	// References the last matched form element (used to set the focus):
	lastFormElementMatch: null,
	// References the current window when filling out forms:
	currentWindow: null,
	// Holds the index of the current form when filling out forms:
	currentFormIndex:null,
	// Holds the index of the current form element when filling out forms:
	currentElementIndex: null,
	// References the target form field on which the context menu has been invoked:
	targetFormField: null,
	// Event listener for the content area context menu:
	contentAreaContextMenuEventListener: null,
	// Holds the the tooltip grid which displays commands and their mouse buttons and keyboard shortcuts:
	tooltipGrid: null,
	// Holds the current profile tooltip label:
	tooltipCurrentProfile: null,
	// The selected help URL source
	helpUrlIndex: null,

	// Cached arrays
	cachedFieldRules: null,
	cachedProfileLabels: null,
	cachedProfileSiteRules: null,

	// Flag for cancelled dialog. false when pressed OK, true when pressed cancel / closed the window
	// will be false if OK was pressed. This is the behaviour in windows. In Linux / OSX, there is no OK button
	flagCancelDialog: true,

	myLog: function(msg) {
		msg = "autofill : " + msg + " ("+ new Date().toLocaleTimeString()+")";
		// https://developer.mozilla.org/en/Components.utils.reportError
		var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
		consoleService.logStringMessage(msg);
	},

	initialize: function() {
		this.myLog("Logging enabled");
		Firebug.Console.log("Logging enabled");

		// Save the reference to the Autofill Forms preferences branch:
		this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
		
		// Add a preferences observer to the autofillForms preferences branch:
		this.autofillFormsPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
	 	this.autofillFormsPrefs.addObserver('', this, false);

		// Implement the event listener for the content area context menu:
		this.contentAreaContextMenuEventListener = function(event) {
			autofillForms.initContentAreaContextMenu(event);
		}
	 	
		// Initialize the preferences settings:
		this.initializePrefs();

		//Disable the highlight if needed:
		var doc = this.getDoc(this.getWin());
		if (doc && doc.body.getBoundingClientRect) {
			this.myLog("I have it!");
			var item = document.getElementById('autofillFormsHighlight');
			if (item) {
				this.myLog("I have found the item!");
				item.setAttribute('disabled', 'true');
			}
		}
	},
	
	initContentAreaContextMenu: function(event) {	
		var cm0 = document.getElementById('autofillFormsContextMenuItem');
		var cm1 = document.getElementById('autofillFormsContextMenu');
		var cm2 = document.getElementById('autofillFormsManualFillContextMenu');
		var cm3 = document.getElementById('autofillFormsAddRuleContextMenuItem');
		var cm4 = document.getElementById('autofillFormsAddFormAsProfileContextMenuItem');
		var cm5 = document.getElementById('autofillFormsContextMenuSeparator1');
		var cm6 = document.getElementById('autofillFormsContextMenuSeparator2');
		var cm7 = document.getElementById('autofillFormsDisplayFormDetailsContextMenuItem');
		if(cm0 && gContextMenu) {
			if(gContextMenu.target && this.isValidFormField(gContextMenu.target)) {
				cm0.hidden = true;
				cm1.hidden = true;
				if(this.autofillFormsPrefs.getBoolPref('hideFormFieldsContextMenu')) {
					cm2.hidden = true;
					cm3.hidden = true;
					cm4.hidden = true;
					cm5.hidden = true;
					cm6.hidden = true;
					cm7.hidden = true;
					this.targetFormField = null;
				} else {
					cm2.hidden = false;
					cm3.hidden = false;
					cm4.hidden = false;
					// Show menuseparators if not already separated:
					if(this.isPreviousNodeSeparated(cm5)) {
						cm5.hidden = true;
					} else {
						cm5.hidden = false;
					}
					if(this.isNextNodeSeparated(cm6)) {
						cm6.hidden = true;
					} else {
						cm6.hidden = false;
					}
					this.targetFormField = gContextMenu.target;
				}
				return;
			}

			if(this.autofillFormsPrefs.getBoolPref('hideContextMenuItem')
				|| gContextMenu.isContentSelected
				|| gContextMenu.onTextInput
				|| gContextMenu.onImage
				|| gContextMenu.onLink
				|| gContextMenu.onCanvas
				|| gContextMenu.onMathML
				|| !this.getDoc().forms
				|| !this.getDoc().forms.length) {
				cm0.hidden = true;
				cm1.hidden = true;
				cm5.hidden = true;
				cm6.hidden = true;
				cm7.hidden = true;
			} else {
				if(this.getProfileLabels().length == 1) {
					cm0.hidden = false;
					cm1.hidden = true;					
				} else {
					cm0.hidden = true;
					cm1.hidden = false;
				}
				// Show menuseparators if not already separated:
				if(this.isPreviousNodeSeparated(cm5)) {
					cm5.hidden = true;
				} else {
					cm5.hidden = false;
				}
				if(this.isNextNodeSeparated(cm6)) {
					cm6.hidden = true;
				} else {
					cm6.hidden = false;
				}
				cm7.hidden = false;
			}
			cm2.hidden = true;
			cm3.hidden = true;
			cm4.hidden = true;
			this.targetFormField = null;
		}
	},
	
	isNextNodeSeparated: function(node) {
		while(node) {
			node = node.nextSibling
			if(node.hidden) {
				continue;
			}
			if(node.nodeName == 'menuseparator') {
				return true;
			} else {
				return false;
			}
		}
		return true;
	},
	
	isPreviousNodeSeparated: function(node) {
		while(node) {
			node = node.previousSibling;
			if(node.hidden) {
				continue;
			}
			if(node.nodeName == 'menuseparator') {
				return true;
			} else {
				return false;
			}
		}
		return true;
	},
	
	initializePrefs: function() {
		this.myLog("initialize prefs");

		// Initialize the keyboard shortcut object container:
		this.shortcut = new Object();
		this.shortcut['shortcut'] = null;
		this.shortcut['shortcutSubmit'] = null;
		this.shortcut['shortcutAllTabs'] = null;
		this.shortcut['shortcutFromProfileSelection'] = null;
		this.shortcut['shortcutProfile'] = null;
		this.shortcut['shortcutSettings'] = null;
		this.shortcut['shortcutDisplayFormDetails'] = null;
		for(var property in this.shortcut) {
			this.updateShortcut(property);
		}
		// Initialize toolbar and statusbar icons and context menu:
		this.hideToolbarButtonUpdate();
		this.hideToolbarButtonMenuUpdate();
		this.hideStatusbarIconUpdate();
		this.hideContextMenuItemUpdate();
	},

	observe: function(subject, topic, data) {
		// Only observe preferences changes:
		if (topic != 'nsPref:changed')
			return;
		this.myLog("Preference change");
		switch(data) {
			case 'profileIndex':
				// If set to null, the profileIndex will be updated on next getProfileIndex() call:
				this.profileIndex = null;
				break;
			case 'globalProfileIndex':
				// If set to null, the globalProfileIndex will be updated on next getGlobalProfileIndex() call:
				this.globalProfileIndex = null;
				break;
			case 'formFieldsContextMenuProfileIndex':
				// If set to null, the formFieldsContextMenuProfileIndex will be updated on next getFormFieldsContextMenuProfileIndex() call:
				this.formFieldsContextMenuProfileIndex = null;
				break;
			case 'profileLabels':
				// If set to null, the profileLabels will be updated on next getProfileLabels() call:
				this.profileLabels = null;
				break;
			case 'profileSiteRules':
				// If set to null, the profileSiteRules will be updated on next getProfileSiteRules() call:
				this.profileSiteRules = null;
				break;
			case 'shortcut':
				this.updateShortcut('shortcut');
				this.tooltipGrid = null;
				break;
			case 'shortcutSubmit':
				this.updateShortcut('shortcutSubmit');
				this.tooltipGrid = null;
				break;
			case 'shortcutAllTabs':
				this.updateShortcut('shortcutAllTabs');
				this.tooltipGrid = null;
				break;
			case 'shortcutFromProfileSelection':
				this.updateShortcut('shortcutFromProfileSelection');
				this.tooltipGrid = null;
				break;
			case 'shortcutProfile':
				this.updateShortcut('shortcutProfile');
				this.tooltipGrid = null;
				break;
			case 'shortcutSettings':
				this.updateShortcut('shortcutSettings');
				this.tooltipGrid = null;
				break;
			case 'shortcutDisplayFormDetails':
				this.updateShortcut('shortcutDisplayFormDetails');
				this.tooltipGrid = null;
				break;
			case 'mouseShortcut':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcut'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'mouseShortcutSubmit':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcutSubmit'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'mouseShortcutAllTabs':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcutAllTabs'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'mouseShortcutFromProfileSelection':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcutFromProfileSelection'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'mouseShortcutProfile':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcutProfile'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'mouseShortcutSettings':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcutSettings'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'mouseShortcutDisplayFormDetails':
				if(this.mouseButton) {
					this.mouseButton['mouseShortcutDisplayFormDetails'] = null;
					this.tooltipGrid = null;
				}
				break;
			case 'fieldRules':
				if(!this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
					// If set to null, the fieldRules will be updated on next getFieldRules() call:
					this.fieldRules = null;
				}
				break;
			case 'storeEncrypted':
				// To update the stored data, we must decrypt or may not decrypt
				// the prefString in opposition to the setting which just changed -
				// the "invertedSetting" helper var helps to identify this situation:
				this.invertedSetting = true;
				// Store data encrypted/decrypted:
				this.setFieldRules();
				this.invertedSetting = false;
				break;
			case 'dynamicTags':
				// If set to null, the dynamicTags will be updated on next getDynamicTags() call:
				this.dynamicTags = null;
				break;
			case 'dynamicTagCodes':
				// If set to null, the dynamicTagCodes will be updated on next getDynamicTagCodes() call:
				this.dynamicTagCodes = null;
				break;
			case 'hideContextMenuItem':
				this.hideContextMenuItemUpdate();
				break;
			case 'hideFormFieldsContextMenu':
				this.hideContextMenuItemUpdate();
				break;
			case 'hideStatusbarIcon':
				this.hideStatusbarIconUpdate();
				break;
			case 'hideToolbarButton':
				this.hideToolbarButtonUpdate();
				this.hideToolbarButtonMenuUpdate();
				break;
			case 'hideToolbarButtonMenu':
				this.hideToolbarButtonMenuUpdate();
				break;
			case 'useConfigDirectory':
				if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
					this.exportToConfigDirectory();
				} else {
					this.importFromConfigDirectory();
				}
				break;
			case 'autoSelectBestProfile':
				break;
		}
	},
	
	hideContextMenuItemUpdate: function() {
		var contentAreaContextMenu = document.getElementById('contentAreaContextMenu');
		if(contentAreaContextMenu) {
			if(!this.autofillFormsPrefs.getBoolPref('hideContextMenuItem') 
				|| !this.autofillFormsPrefs.getBoolPref('hideFormFieldsContextMenu')) {
				// Add the content area context menu listener:
				contentAreaContextMenu.addEventListener(
					'popupshowing',
					this.contentAreaContextMenuEventListener,
					false
				);
			} else {
				var cm0 = document.getElementById('autofillFormsContextMenuItem');
				var cm1 = document.getElementById('autofillFormsContextMenu');
				var cm2 = document.getElementById('autofillFormsManualFillContextMenu');
				var cm3 = document.getElementById('autofillFormsAddRuleContextMenuItem');
				var cm4 = document.getElementById('autofillFormsAddFormAsProfileContextMenuItem');
				var cm5 = document.getElementById('autofillFormsContextMenuSeparator1');
				var cm6 = document.getElementById('autofillFormsContextMenuSeparator2');
				if(cm0) {
					cm0.hidden = true;
					cm1.hidden = true;
					cm2.hidden = true;
					cm3.hidden = true;
					cm4.hidden = true;
					cm5.hidden = true;
					cm6.hidden = true;
				}
				// Remove the content area context menu listener:
				this.targetFormField = null;
				contentAreaContextMenu.removeEventListener(
					'popupshowing',
					this.contentAreaContextMenuEventListener,
					false
				);
			}
		}
	},
	
	hideStatusbarIconUpdate: function() {
		// Change the statusbar icon visibility:
		var autofillFormsPanelIcon = document.getElementById('autofillFormsPanelIcon');
		if(autofillFormsPanelIcon) {
			autofillFormsPanelIcon.setAttribute(
				'hidden',
				this.autofillFormsPrefs.getBoolPref('hideStatusbarIcon')
			);
		}
	},

	installToolbarButton: function(buttonID, beforeNodeID, toolbarID) {
		beforeNodeID = beforeNodeID ? beforeNodeID : 'urlbar-container';
		toolbarID = toolbarID ? toolbarID : 'nav-bar';
		var button = document.getElementById(buttonID);
		if(!button || button.parentNode.localName == 'toolbarpalette') {
			var toolbar = document.getElementById(toolbarID);
			if(!toolbar) {
				// mid-browser??:
				toolbar = document.getElementById('navigation-toolbar');
			}			
			if(toolbar && 'insertItem' in toolbar) {
				var beforeNode = document.getElementById(beforeNodeID);
				if(!beforeNode) {
					// SeaMonkey
					beforeNode = document.getElementById('nav-bar-inner');
				}			
				if(beforeNode && beforeNode.parentNode != toolbar) {
					beforeNode = null;
				}
				// Insert before the given node or at the end of the toolbar if the node is not available:
				toolbar.insertItem(buttonID, beforeNode, null, false);
				toolbar.setAttribute('currentset', toolbar.currentSet);
				document.persist(toolbar.id, 'currentset');
			}
		}
	},

	hideToolbarButtonUpdate: function() {
		var autofillFormsButton = document.getElementById('autofillFormsButton');
		var hideToolbarButton = this.autofillFormsPrefs.getBoolPref('hideToolbarButton');
		if(!autofillFormsButton && !hideToolbarButton) {
			// Add the toolbar button to the toolbar:
			this.installToolbarButton('autofillFormsButton');
			autofillFormsButton = document.getElementById('autofillFormsButton');
		}
		else if(autofillFormsButton &&
				autofillFormsButton.parentNode.localName == 'toolbarpalette' &&
				!hideToolbarButton) {
			// Add the toolbar button to the toolbar:
			this.installToolbarButton('autofillFormsButton');
			autofillFormsButton = document.getElementById('autofillFormsButton');
		}
		if(autofillFormsButton) {
			autofillFormsButton.setAttribute(
				'hidden',
				hideToolbarButton
			);
		}
	},
	
	hideToolbarButtonMenuUpdate: function() {
		var autofillFormsButton = document.getElementById('autofillFormsButton');
		if(autofillFormsButton) {
			if(this.autofillFormsPrefs.getBoolPref('hideToolbarButtonMenu')) {
				autofillFormsButton.removeAttribute('type');
			} else {
				autofillFormsButton.setAttribute('type','menu-button');
			}
		}
	},

	commandHandler: function(event) {
		if(typeof event.button == 'undefined') {
			// If no event.button is set, the command has been done by the left mouse button:
			event.button = 0;
		}
		// Recognize the mouse button and perform the associated action:
		var mouseButtonObj = this.recognizeMouseButton(event);
		if(this.getMouseButton('mouseShortcut').equals(mouseButtonObj)) {
			this.fillForms();
		} else if(this.getMouseButton('mouseShortcutSubmit').equals(mouseButtonObj)) {
			this.fillForms(null, null, true);
		} else if(this.getMouseButton('mouseShortcutAllTabs').equals(mouseButtonObj)) {
			this.fillForms(null, null, null, true);
		} else if(this.getMouseButton('mouseShortcutFromProfileSelection').equals(mouseButtonObj)) {
			this.profileSelectionFormFillPopup(event);
		} else if(this.getMouseButton('mouseShortcutProfile').equals(mouseButtonObj)) {
			this.showProfileSwitcher(event);
		} else if(this.getMouseButton('mouseShortcutSettings').equals(mouseButtonObj)) {
			this.showDialog('chrome://autofillForms/content/autofillFormsOptions.xul');
		} else if(this.getMouseButton('mouseShortcutDisplayFormDetails').equals(mouseButtonObj)) {
			this.displayFormDetails();
		}
	},
	
	clickHandler: function(event) {
		switch(event.button) {
			case 0:
				// The left mouse button is already handled for clicks on the toolbar button,
				// but not for clicks on the status bar icon:
				if(event.target.id == 'autofillFormsPanelIcon') {
					this.commandHandler(event);
				}
				break;
			default:
				this.commandHandler(event);
		}
	},
	
	profileSelectionFormFillPopup: function(event) {
		var popup = document.getElementById('autofillFormsProfileSelectionPopup');		
		if(popup && typeof popup.openPopup == 'function') {
			this.prepareProfileSelectionFormFillMenu(popup);
			// Show the popup menu (only available for Firefox >= 3):
			popup.openPopup(event.target, null, 0, 0, false, true);
		} else {
			this.profileSelectionFormFillPrompt(event);
		}
	},
	
	prepareProfileSelectionFormFillMenu: function(menupopup) {
		// Remove all children nodes:
		while(menupopup.hasChildNodes()) {
			menupopup.removeChild(menupopup.firstChild);
		}
		var menuitem = document.createElement('menuitem');
		menuitem.setAttribute('class','menuitem-iconic autofillFormsIcon');
		// Add the profile labels as menu items:
		for(var i=0; i < this.getProfileLabels().length; i++) {
			menuitem = menuitem.cloneNode(false);
			menuitem.setAttribute('label', this.getProfileLabel(i));
			menuitem.setAttribute('oncommand', 'autofillForms.fillForms(null,'+i+');');
			menupopup.appendChild(menuitem);
		}
	},
	
	profileSelectionFormFillPrompt: function(event) {
		// Show a profile selection prompt and fill out forms with the selected profile:
		var list = this.getProfileLabels();
		var selected = {};
		var ok = this.getPrompts().select(
			window,
			this.getStringBundle().getString('profileSelectionFormFillTitle'),
			this.getStringBundle().getString('profileSelectionPrompt'),
			list.length,
			list,
			selected
		);
		if(ok) {
			this.fillForms(null, selected.value);
		}
	},
	
	fillForms: function(win, profileIndex, autoSubmit, allTabs) {
		if(!win || !win.document) {
			win = this.getWin();
		}
		
		var currentProfileIndex = this.getProfileIndex();
		var toggleAutoSelectBestProfile;
		if(typeof profileIndex == 'number') {
			// Temporarily set the given profile index:
			this.setProfileIndex(profileIndex);
			
			if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
				// Temporarily disable autoSelectBestProfile:
				this.autofillFormsPrefs.setBoolPref('autoSelectBestProfile', false);
				toggleAutoSelectBestProfile = true;
			}
		}
		
		autoSubmit = autoSubmit ? autoSubmit : null;
		
		if(allTabs) {
			// Fill out forms on all open browser tabs:
			for(var i=0; i<this.getBrowser().browsers.length; i++) {
				this.searchAndFillForms(
					this.getBrowser().getBrowserAtIndex(i).contentWindow,
					autoSubmit
				);
			}
		} else {
			// Fill out forms on the current tab (or the given window object):
			this.searchAndFillForms(win, autoSubmit);	
		}

		// Reset Alternatives (including the cache):
		this.fieldRuleAlternativesIndex = null;
		this.fieldRuleAlternativesLength = null;
		this.fieldRuleAlternativesHash = null;
		this.fieldRuleAlternativesCache = null;
		
		// Reset objects to release used memory:
		this.fieldRules = null;
		this.profileSiteRules = null;
		this.dynamicTags = null;
		this.dynamicTagCodes = null;
		
		// Reset the selected profile:
		this.setProfileIndex(currentProfileIndex);
		
		if(toggleAutoSelectBestProfile) {
			// Reenable autoSelectBestProfile:
			this.autofillFormsPrefs.setBoolPref('autoSelectBestProfile', true);
		}
	},
	
	searchAndFillForms: function(win, autoSubmit) {
		var doc = this.getDoc(win);
		
		// Check if any web forms are available on the current window:
		if(doc && doc.forms && doc.forms.length > 0) {

			var url = doc.location.href;

			if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
				// Remember the currently selected profile:
				var currentProfileIndex = this.getProfileIndex();
			}
			
			// Select the best matching profile - returns false if none matches:
			if(!this.selectBestMatchingProfile(url)) {
				return;
			}

			this.currentWindow = win;

			// Holds the form to be submitted:
			var submitForm;
			// Holds the first submit element found on the form:
			var submitElement;
			
		 	// Go through the forms:
		 	for(var i = 0; i < doc.forms.length; i++) {
				this.currentFormIndex = i;
		 		
		 		// The form elements list:
				var elements = doc.forms[i].elements;
				
				// A hash to store the alternatives for radio input fields:
				this.fieldRuleAlternativesHash = new Object();
				
				// Go through the form elements:
				for(var j = 0; j < elements.length; j++) {					
					this.currentElementIndex = j;
					
					// Fill out valid form field types:
					if(this.isValidFormField(elements[j])) {
						this.setFormField(elements[j], url);
					}
					
					// Collect the first submit button of the form if autoSubmit is enabled:
					if(autoSubmit && elements[j].type && elements[j].type == 'submit' && !submitElement) {
						submitElement = elements[j];
					}
				}
				
				this.applyStoredFieldRulesAlternatives();
				
				if(autoSubmit) {
					if(this.lastFormElementMatch && this.lastFormElementMatch.form == doc.forms[i]) {
						// Elements have been matched on this form, check the submitElement:
						if(!submitElement) {
							submitElement = this.getImageSubmitButton(doc.forms[i]);
						}
						submitForm = doc.forms[i];
						// Break out of the forms loop:
						break;
					} else {
						submitElement = null;
					}
				}
			}
			
			if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
				// Reset the selected profile to the manually selected one:
				this.setProfileIndex(currentProfileIndex);
			}

			if(this.lastFormElementMatch && this.autofillFormsPrefs.getBoolPref('focusLastFormElementMatch')) {
				// Set the focus to the last matched form element:
				this.lastFormElementMatch.focus();
			}

			// Reset the last matched form element:
			this.lastFormElementMatch = null;
			
			this.currentWindow = null;
			this.currentFormIndex = null;
			this.currentElementIndex = null;
			
			if(autoSubmit && submitForm) {
				// autoSubmit the form with a click on the submit button if found
				// or else by calling the submit() method on the form:
				if(submitElement) {
					submitElement.click();
				} else {
					submitForm.submit();
				}
			}
		}

		// Recursive call for all subframes:
		for(var f=0; f < win.frames.length; f++) {
			this.searchAndFillForms(win.frames[f], autoSubmit);
		}
	},
	
	getImageSubmitButton: function(form) {
	 	var inputElements = form.getElementsByTagName('input');
	 	for(var i = 0; i < inputElements.length; i++) {
			if(inputElements[i].type == 'image') {
				return inputElements[i];
			}
	 	}
		return null;
	},

	selectBestMatchingProfile: function(url) {
		if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
			var match;
			// The emtpy siteRule (?:) has a match length of 0, so we set the initial value to -1:
			var maxMatch = -1;
			var index = -1;
			// First test the currently selected profile:
			try {
				match = url.match(new RegExp(this.getProfileSiteRule(this.getProfileIndex()),'i'));
				if(match && (match.toString()).length > maxMatch) {
					maxMatch = (match.toString()).length;
					index = this.getProfileIndex();
				}
			} catch(e) {
				// Catch errors caused by invalid profile site rules
			}	
			for(var i=0; i<this.getProfileSiteRules().length; i++) {
				if(i == this.getProfileIndex()) {
					// Skip the current profile (already tested):
					continue;
				}
				try {
					match = url.match(new RegExp(this.getProfileSiteRule(i),'i'));
					if(match && (match.toString()).length > maxMatch) {
						maxMatch = (match.toString()).length;
						index = i;
					}
				} catch(e) {
					// Catch errors caused by invalid profile site rules
				}
			}
			if(index > -1) {
				// Select the profile with the best match:
				this.setProfileIndex(index);
				return true;
			}
		} else {
			try {
				var regExp = new RegExp(this.getProfileSiteRule(this.getProfileIndex()),'i');
				if(regExp.test(url)) {
					return true;
				}
			} catch(e) {
				// Catch errors caused by invalid profile site rules
			}
		}
		return false;
	},
	
	setFormField: function(element,url) {
		var matchFound = false;
		
		// Apply the fieldRules of the current profile:
		matchFound = this.applyFieldRulesOnElement(element,url,this.getFieldRules());
		
		// If no match has been found, apply the fieldRules of the global profile, if enabled:
		if(!matchFound && this.autofillFormsPrefs.getBoolPref('enableGlobalProfile')) {
			// Only apply the global profile fieldRules if the current profile is not the global profile;
			if(this.getProfileIndex() != this.getGlobalProfileIndex()) {
				// Only apply the global profile if the global profile site rule matches the url:
				try {
					var regExp = new RegExp(this.getProfileSiteRule(this.getGlobalProfileIndex()),'i');
					if(regExp.test(url)) {
						matchFound = this.applyFieldRulesOnElement(element,url,this.getGlobalFieldRules());
					}
				} catch(e) {
					// Catch errors caused by invalid profile site rules
				}
			}
		}

		// Highlight styles:
		var highlightStyleMatch = this.autofillFormsPrefs.getCharPref('highlightStyleMatch');
		var highlightStyleNoMatch = this.autofillFormsPrefs.getCharPref('highlightStyleNoMatch');

		if(matchFound) {
			// Set the current element as the last matched form element:
			this.lastFormElementMatch = element;
			
			if(highlightStyleMatch) {
				// Highlight matched form fieds:
				element.setAttribute('style', highlightStyleMatch);	
			}
		} else if(highlightStyleNoMatch) {
			// Highlight not matched form fieds:
			element.setAttribute('style', highlightStyleNoMatch);
		}
	},

	getIndexForFieldRules: function(fieldRules) {
		if(this.fieldRules) {
			for(var i=0; i<this.fieldRules.length; i++) {
				if(this.fieldRules[i] === fieldRules) {
					return i;
				}
			}
		}
		return -1;
	},
	
	fieldRuleAlternativeFactory: function(fieldRules, index) {
		var af = this;
		if(typeof arguments.callee.fieldRuleAlternative == 'undefined') {
			arguments.callee.fieldRuleAlternative = function(fieldRules, index) {
				this.fieldRules = fieldRules;
				this.index = index;
				this.fieldRule = this.fieldRules[this.index];
				return this;
			}
			arguments.callee.fieldRuleAlternative.prototype = {
				af : af,
				fieldRuleValue: null,
				fieldRuleValueRegExp: null,
				fieldRuleRegExp: null,
				siteRuleRegExp: null,
				optionsIndex: null,
				element: null,
				getValue: function() {
					if(this.fieldRuleValue === null) {
						// Replace dynamic tags if enabled:
						if(this.af.autofillFormsPrefs.getBoolPref('enableDynamicTags'))
							this.fieldRuleValue = this.af.replaceDynamicTags(this.fieldRule['fieldRuleValue']);
						else
							this.fieldRuleValue = this.fieldRule['fieldRuleValue'];				
					}
					return this.fieldRuleValue;
				},
				getRule: function() {
					return this.fieldRule['fieldRuleFieldRule'];
				},
				getName: function() {
					return this.fieldRule['fieldRuleName'];
				},
				isEnabled: function() {
					return this.fieldRule['fieldRuleEnabled'];
				},
				isURLMatching: function(url) {
					if(this.siteRuleRegExp === null) {
						this.siteRuleRegExp = new RegExp(this.fieldRule['fieldRuleSiteRule'],'i');
					}
					// Test if the siteRule matches the given URL:
					return this.siteRuleRegExp.test(url);
				},
				isRuleMatching: function(str) {
					if(this.fieldRuleRegExp === null) {
						this.fieldRuleRegExp = new RegExp(this.fieldRule['fieldRuleFieldRule'],'i');
					}
					// Test if the fieldRule matches the given string:
					return this.fieldRuleRegExp.test(str);
				},
				isValueMatching: function(str) {
					try {
						if(this.fieldRuleValueRegExp === null) {
							this.fieldRuleValueRegExp = new RegExp(this.getValue(),'i');
						}
						// Test if the value as regular expression matches the given string:
						return this.fieldRuleValueRegExp.test(str);
					} catch(e) {
						// If turning the value into a regular expression fails, compare the strings:
						return (str == this.getValue());
					}
				},
				isOverwrite: function() {
					// This setting defines if existing field contents should be overwritten
					// and if checkboxes and radio buttons should be checked or unchecked
					// and if selection options should be selected or unselected:
					return this.fieldRule['fieldRuleOverwrite']
				},
				getIndex: function() {
					return this.index;
				},
				getOptionsIndex: function() {
					return this.optionsIndex;
				},
				setOptionsIndex: function(optionsIndex) {
					this.optionsIndex = optionsIndex;
				},
				getElement: function() {
					return this.element;
				},
				setElement: function(element) {
					this.element = element;
				},
				clone: function() {
					// This creates only a shallow copy,
					// though we only need a shallow copy:
					var clone = new this.constructor();
					for(var key in this) {
						clone[key] = this[key];
					}
					return clone;
				}
			}
		}
		if(this.fieldRuleAlternativesCache == null) {
			this.fieldRuleAlternativesCache = new Object();
		}
		var identifier = this.getIndexForFieldRules(fieldRules)+'-'+index;
		if(!this.fieldRuleAlternativesCache[identifier]) {
			this.fieldRuleAlternativesCache[identifier] = new arguments.callee.fieldRuleAlternative(
				fieldRules,
				index
			);
		} else {
			// Clone the cached alternative and set the clone as new cached element:
			this.fieldRuleAlternativesCache[identifier] = this.fieldRuleAlternativesCache[identifier].clone()
		}
		return this.fieldRuleAlternativesCache[identifier];
	},

	getLabelForElement: function(element) {
		if(element.form && element.id) {
			// Method to retrieve the textual content of the label assigned to the form element:
			var labels = element.form.getElementsByTagName('label');
			for(var i=0; i<labels.length; i++) {
				if(labels[i].htmlFor && labels[i].htmlFor == element.id) {
					// label elements may contain other inline elements,
					// so we just use the innerHTML content and strip it of all HTML tags
					// whitespace is removed from the beginning and end of the string for convenience:
					return this.trim(this.stripTags(labels[i].innerHTML));
				}
			}
		}
		if(!this.autofillFormsPrefs.getBoolPref('labelsStrictMode')) {
			return this.getLabelCloseToElement(element);
		}
		return null;
	},
	
	getLabelCloseToElement: function(element) {
		var label = null;
		var node = element;
		var nextNode;
		if(element.type == 'checkbox' || element.type == 'radio') {
			// For checkboxes and radio buttons the label is usually placed as nextSibling:
			nextNode = 'nextSibling';
		} else {
			// For other elements the label is usually placed as previousSibling:
			nextNode = 'previousSibling';
		}
		// Check if a sibling contains the element label:
		while(node[nextNode]) {
			node = node[nextNode];
			label = this.getNodeTextContent(node, true);
			if(label) {
				return label;
			}
		}
		// Parse the siblings of the parentNode:
		node = element.parentNode;
		if(node) {
			while(node[nextNode]) {
				node = node[nextNode];
				label = this.getNodeTextContent(node, true);
				if(label) {
					return label;
				}
			}
			// If the parentNode of the parentNode is a table cell,
			// also parse the siblings of this node:
			node = element.parentNode.parentNode;
			if(node && node.nodeName == 'TD') {
				while(node[nextNode]) {
					node = node[nextNode];
					label = this.getNodeTextContent(node, true);
					if(label) {
						return label;
					}
				}
			}
		}
		return null;
	},
	
	getNodeTextContent: function(node, trim) {
		// Get the text content from the current node or its child nodes:
		var text;		
		if(node.nodeType == 3) {
			// nodeType 3 is a text node:
			text = node.nodeValue;
		} else {
			// Do not follow selection nodes, script nodes or noscript nodes:
			if(node.nodeName == 'SELECT' || node.nodeName == 'SCRIPT' || node.nodeName == 'NOSCRIPT') {
				return '';
			}
			text = '';
			for(var i=0; i<node.childNodes.length; i++) {
				text += this.getNodeTextContent(node.childNodes[i]);
			}
		}
		if(trim) {
			return this.trim(text);
		} else {
			return text;
		}
	},
	
	applyFieldRulesOnElement: function(element,url,fieldRules) {

		var labelValue = this.autofillFormsPrefs.getBoolPref('matchAgainstLabels') ?
			this.getLabelForElement(element) : null;

		var positionString = this.autofillFormsPrefs.getBoolPref('matchAgainstPositions') ?
			this.currentFormIndex + this.autofillFormsPrefs.getCharPref('positionsIdentifier')
			+ this.currentElementIndex : null;
			
		var fieldRuleAlternatives = new Array();
		
		// Go through the list of fieldRules:
		for(var i=0; i < fieldRules.length; i++) {
			
			var rule = this.fieldRuleAlternativeFactory(fieldRules, i);
			
			// Skip this rule if
			// a) the rule is disabled and disabled rules are to be ignored or
			// b) the current URL is not matching the siteRule or
			// c) all of the following are false:
			// 	1) the element name does not match the fieldRule
			// 	2) label matching is disabled or the element label does not match the fieldRule
			// 	3) the element name is not empty or the element id does not match the fieldRule
			//	4) position matching is disabled or the position does not match the fieldRule
			if(	!rule.isEnabled() &&
				this.autofillFormsPrefs.getBoolPref('ignoreDisabledRulesOnAutofill') ||
				!rule.isURLMatching(url) ||
				(
					!rule.isRuleMatching(element.name) &&
					(labelValue === null || !rule.isRuleMatching(labelValue)) &&
					(element.name || !rule.isRuleMatching(element.id)) &&
					(positionString === null || !rule.isRuleMatching(positionString))
				)
				) {
				if(fieldRuleAlternatives.length > 0) {
					// Break out of the loop, if we already have an alternative:
					break;
				} else {
					continue;
				}
			}
	
			if(element.type == 'select-one' || element.type == 'select-multiple') {
				// Go through the selection options:
				for(var j = 0; j < element.options.length; j++) {
					// Match either the value or the text (the selection option label):
					if(rule.isValueMatching(element.options[j].value) || rule.isValueMatching(element.options[j].text)) {
						// Remember the matching option:
						rule.setOptionsIndex(j);
						// Remember the element:
						rule.setElement(element);
						// Add a clone of the alternative and continue to see if the value matches several options:
						fieldRuleAlternatives.push(rule.clone());
					}
				}
			} else if(element.type == 'checkbox' || element.type == 'radio') {
				if(rule.isValueMatching(element.value)) {
					// Remember the element:
					rule.setElement(element);
					// Add the alternative:
					fieldRuleAlternatives.push(rule);
					// Only one rule has to match a checkbox/radio button, so we break out of the loop:
					break;
				}
			} else {				
				// Remember the element:
				rule.setElement(element);
				// Add the alternative:
				fieldRuleAlternatives.push(rule);
			}
			if(this.autofillFormsPrefs.getBoolPref('callOnChangeAfterFillingFields')) {
				this.fireEvent(element,'change')
			}
		}

		return this.applyFieldRulesAlternativesOnElement(element,fieldRuleAlternatives);
	},

	applyFieldRulesAlternativesOnElement: function(element,fieldRuleAlternatives) {
		if(fieldRuleAlternatives.length == 0) {
			return false;
		}

		if (this.autofillFormsPrefs.getBoolPref('focusLastFormElementMatch')) {
			element.focus();
		}

		// Add a box (with some help from Mike Ratcliffe)
		// http://groups.google.com/group/firebug/browse_thread/thread/7d4bd89537cd24e7/2c9483d699efe257?hl=en
		var doc = this.getDoc();
		var div1 = doc.createElement('div');

		if (this.autofillFormsPrefs.getBoolPref('highlightFormElements') && doc.body.getBoundingClientRect) {
			var rectBody = doc.body.getBoundingClientRect();
			var rect = element.getBoundingClientRect();
			var hasDiv1 = false;

			div1.setAttribute('id', 'autofillFormsHighlight');
			div1.setAttribute('style', 'position:absolute;z-index:2147483646;'
					+ this.autofillFormsPrefs.getCharPref('formHighlightStyle')
					+ ';left:'+(rect.left-rectBody.left)+'px'
					+ ';top:'+(rect.top-rectBody.top)+'px'
					+ ';width:'+(rect.width-3)+'px'
					+ ';height:'+(rect.height-3)+'px'
					);
			doc.body.appendChild(div1);
			div1.scrollIntoView();
			hasDiv1 = true;
		}
		
		// Use all alternatives for select-multiple elements:
		if(element.type == 'select-multiple') {
			for(var i=0; i < fieldRuleAlternatives.length; i++) {
				var rule = fieldRuleAlternatives[i];				
				if(rule.isOverwrite()) {
					element.options[rule.getOptionsIndex()].selected = true;
				} else {
					element.options[rule.getOptionsIndex()].selected = false;
				}
			}
			if (hasDiv1) doc.body.removeChild(div1);
			return true;
		}
		
		// Select the alternatives index (displays a selection dialog if required):
		var index = this.selectFieldRulesAlternativesIndex(fieldRuleAlternatives);
		
		if(index == -1) {
			if (hasDiv1) doc.body.removeChild(div1);
			return false;
		} else {
			var rule = fieldRuleAlternatives[index];
			if(element.type == 'select-one') {
				if(rule.isOverwrite()) {
					element.options[rule.getOptionsIndex()].selected = true;
				} else {
					element.options[rule.getOptionsIndex()].selected = false;
				}				
			} else if(element.type == 'checkbox') {
				if(rule.isOverwrite()) {
					element.checked = true;
				} else {
					element.checked = false;
				}
			} else if(element.type == 'radio') {
				try {
					// Rules matching radio elements are stored and handled as group
					// at the end of each form loop with the applyStoredFieldRulesAlternatives method:
					if(!this.fieldRuleAlternativesHash[element.name]) {
						this.fieldRuleAlternativesHash[element.name] = new Array();
					}
					this.fieldRuleAlternativesHash[element.name].push(rule);
				} catch(e) {
					this.log(e);
					if (hasDiv1) doc.body.removeChild(div1);
					return false;
				}
			} else {
				if(!element.value || rule.isOverwrite()) {
					if(element.type == 'textarea') {
						// Replace control character placeholders:
						element.value = this.replaceControlCharacterPlaceholders(rule.getValue());
					} else {
						element.value = rule.getValue();
					}
				}
			}
			if(this.autofillFormsPrefs.getBoolPref('callOnChangeAfterFillingFields')) {
				this.fireEvent(element,'change')
			}


		}

		//remove the div, not needed anymore
		if (hasDiv1) doc.body.removeChild(div1);
		return true;		
	},

	fireEvent: function(element,anEvent) {
		var evt = document.createEvent("HTMLEvents");
		evt.initEvent(anEvent, true, true ); // event type,bubbling,cancelable
		return !element.dispatchEvent(evt);
	},

	applyStoredFieldRulesAlternatives: function() {
		for(var key in this.fieldRuleAlternativesHash) {
			var fieldRuleAlternatives = this.filterRealFieldRuleAlternatives(
				this.fieldRuleAlternativesHash[key]
			);
			var index = this.selectFieldRulesAlternativesIndex(fieldRuleAlternatives);
			if(index != -1) {
				var rule = fieldRuleAlternatives[index];
				// This is currently only used for radio input fields:
				if(rule.isOverwrite()) {
					rule.getElement().checked = true;
				} else {
					rule.getElement().checked = false;
				}
			}
		}
	},
	
	filterRealFieldRuleAlternatives: function(fieldRuleAlternatives) {
		// Sort the fieldRuleAlternatives by index:
		fieldRuleAlternatives.sort(this.compareFieldRuleAlternativesByIndex);
		// Make sure only real Alternatives (placed next to each other) are included:
		for(var i=1; i<fieldRuleAlternatives.length; i++) {
			// If the fieldRules index is more than one step larger than the previous one,
			// the remaining array items can be sliced off - they are no real Alternatives:
			if(fieldRuleAlternatives[i].getIndex()-1 > fieldRuleAlternatives[i-1].getIndex()) {
				fieldRuleAlternatives = fieldRuleAlternatives.slice(0, i);
				break;
			}
		}
		return fieldRuleAlternatives;
	},
	
	compareFieldRuleAlternativesByIndex: function(ruleA, ruleB) {
		if(ruleA.getIndex() < ruleB.getIndex()) {
			return -1;
		} else if(ruleA.getIndex() > ruleB.getIndex()) {
			return 1;
		}
		return 0;
	},

	getFieldRulesAlternativeLabel: function(rule) {
		// This method returns a label for this alternative
		// to be displayed on the alternatives selection
		switch(rule.getElement().type) {
			case 'select-multiple':
			case 'select-one':
				// Use the options text:
				return rule.getElement().options[rule.getOptionsIndex()].text;
			case 'radio':
			case 'checkbox':
				// Try to retrieve the element label:
				var label = this.getLabelForElement(rule.getElement());
				// Remove the colon, if present:
				if(label && label.charAt(label.length-1) == ':') {
					label = label.substr(0, label.length-1);
				}
				// If no label could be found,
				// use the element value:
				if(!label) {
					label = rule.getElement().value;
				}
				return label;
			default:
				// Use the calculated value:
				return rule.getValue();
		}
	},
	
	selectFieldRulesAlternativesIndex: function(fieldRuleAlternatives) {					
		// Display a selection prompt if we have alternatives and no alternativesIndex has been set yet
		// or the rememberAlternativesIndex setting is false or the saved alternativesLength is different:
		if(fieldRuleAlternatives.length > 1) {
			// When alternatives are disabled, return either 0 (remember the alternative)
			// or cycle through the available alternatives.
			if(this.autofillFormsPrefs.getBoolPref('disableAlternatives') == true) {
				var fieldRuleAlternativesIndex = 0;
				/*
				//todo take into account multiple instances of the form field. Can't increase the index blindly...
				if(this.autofillFormsPrefs.getBoolPref('rememberAlternativesIndex') == false) {
					fieldRuleAlternativesIndex = this.fieldRuleAlternativesIndex;
					if (this.fieldRuleAlternativesIndex == fieldRuleAlternatives.length-1) {
						this.fieldRuleAlternativesIndex = 0;
					}
					else {
						this.fieldRuleAlternativesIndex += 1;
					}
				}
				*/
				return fieldRuleAlternativesIndex;
			}

			if(this.autofillFormsPrefs.getBoolPref('rememberAlternativesIndex') == false
				|| this.fieldRuleAlternativesIndex === null
				|| fieldRuleAlternatives.length != this.fieldRuleAlternativesLength) {
				// The selection list displays the index number and the current fieldRuleValues:
				var list = new Array();
				var maxFigureLength = fieldRuleAlternatives.length.toString().length;
				for(var i=0; i < fieldRuleAlternatives.length; i++) {
					list.push(
						this.addLeadingZeros(i+1, maxFigureLength)
						+ '.  '
						+ this.getFieldRulesAlternativeLabel(fieldRuleAlternatives[i])
						+ '  - '
						+ fieldRuleAlternatives[i].getName()
					);
				}
				var selected = {};
				// Show the selection prompt:
				var ok = this.getPrompts().select(
					window,
					this.getStringBundle().getString('alternativesSelectionWindowTitle'),
					this.getStringBundle().getString('alternativesSelectionPrompt'),
					list.length,
					list,
					selected
				);
				// Save the selected alternatives index, return -1 on cancel:
				if(ok)
					this.fieldRuleAlternativesIndex = selected.value;
				else
					return -1;
					
				this.fieldRuleAlternativesLength = fieldRuleAlternatives.length;
			}
			// Use the fieldRuleAlternative with the selected fieldRuleAlternativesIndex:
			return this.fieldRuleAlternativesIndex;
		} else if(fieldRuleAlternatives.length == 1) {
			return 0;
		}
		return -1;
	},
	
	stripTags: function(str) {
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('<\\/?[^>]+?>', 'g');
		}
		// Return string stripped from HTML tags:
		return str.replace(arguments.callee.regExp, '');
	},
	
	trim: function(str) {
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('(?:^\\s+)|(?:\\s+$)', 'g');
		}
		// Return string with whitespace removed at beginning and end of the string:
		return str.replace(arguments.callee.regExp, '');
	},

	initProfilesPopupMenu: function(event) {
		var menupopup = event.target;
		// Remove all children nodes:
		while(menupopup.hasChildNodes()) {
			menupopup.removeChild(menupopup.firstChild);
		}
		// Add the profile labels as menu items:
		for(var i=0; i < this.getProfileLabels().length; i++) {
			var menuitem = document.createElement('menuitem');
			menuitem.setAttribute('label', this.getProfileLabel(i));
			menuitem.setAttribute('oncommand', 'autofillForms.setProfileIndex('+i+');');
			menuitem.setAttribute('type', 'radio');
			if(i == this.getProfileIndex()) {
				menuitem.setAttribute('checked', true);
			}
			menupopup.appendChild(menuitem);
		}
	},

	initManualFillContextMenu: function(event) {
		var menupopup = event.target;
		// Remove all children nodes:
		while(menupopup.hasChildNodes()) {
			menupopup.removeChild(menupopup.firstChild);
		}

		var authenticationNeeded = false;
		if(this.autofillFormsPrefs.getBoolPref('storeEncrypted')) {
			// Determine if a master password is set and the user has not been authenticated yet:
			authenticationNeeded = this.getMasterSecurityDevice().getInternalKeyToken().needsLogin()
									&& !this.getMasterSecurityDevice().getInternalKeyToken().isLoggedIn();
		}

		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			// Always retrieve the profile labels from file if useConfigDirectory is enabled:
			this.profileLabels = null;
		}

		// Check if only one profile is to be shown:
		if((this.getFormFieldsContextMenuProfileIndex() != -3) || (this.getProfileLabels().length == 1)) {
			var profileIndex = (this.getFormFieldsContextMenuProfileIndex() == -2)
				? this.getProfileIndex() : this.getFormFieldsContextMenuProfileIndex();
			if(authenticationNeeded) {				
				var menuitem = document.createElement('menuitem');
				menuitem.setAttribute('label', this.getProfileLabel(profileIndex)+'...');
				menuitem.setAttribute(
					'oncommand',
					'autofillForms.authenticateAndShowManualFillDialog('+profileIndex+');'
				);
				menupopup.appendChild(menuitem);
			} else {
				this.initManualFillProfileContextMenu(event, profileIndex);
			}
			return;
		}
		
		// Add the profile labels as menus or menuitems if authentication is needed:
		for(var i=0; i < this.getProfileLabels().length; i++) {
			if(authenticationNeeded) {
				var menuitem = document.createElement('menuitem');
				menuitem.setAttribute('label', this.getProfileLabel(i)+'...');
				menuitem.setAttribute(
					'oncommand',
					'autofillForms.authenticateAndShowManualFillDialog('+i+');'
				);
				menupopup.appendChild(menuitem);
			} else {
				var menu = document.createElement('menu');
				menu.setAttribute('label', this.getProfileLabel(i));
	
				// Add a menupopup for each profile:
				var profilemenupopup = document.createElement('menupopup');
				profilemenupopup.setAttribute(
					'onpopupshowing',
					'if(event.target == this) autofillForms.initManualFillProfileContextMenu(event, '+i+');'
				);
				menu.appendChild(profilemenupopup);
				menupopup.appendChild(menu);
			}
		}
	},

	initManualFillProfileContextMenu: function(event, profileID) {
		var menupopup = event.target;
		// Remove all children nodes:
		while(menupopup.hasChildNodes()) {
			menupopup.removeChild(menupopup.firstChild);
		}
		var menuPopupMore;
		// Add the profile field rules as menu items:
		for(var i=0; i < this.getFieldRules(profileID).length; i++) {
			var menuitem = document.createElement('menuitem');
			menuitem.setAttribute('label', this.getFieldRules(profileID)[i]['fieldRuleName']);
			menuitem.setAttribute('oncommand', 'autofillForms.fillTargetFormField('+profileID+','+i+');');
			if(this.getFieldRules(profileID)[i]['fieldRuleEnabled']) {
				menupopup.appendChild(menuitem);
			} else {
				// Add disabled items to a "More..." menu:
				if(!menuPopupMore) {
					menuPopupMore = document.createElement('menupopup');
				}
				menuPopupMore.appendChild(menuitem);
			}
		}
		if(menuPopupMore) {
			if(!menupopup.hasChildNodes()) {
				// All field rules of this profile are disabled, so no need to create a submenu:
				while(menuPopupMore.hasChildNodes()) {
					// appendChild removes the node from the current parent node
					// and adds it to the new parent node:
					menupopup.appendChild(menuPopupMore.firstChild);
				}
			} else {
				// Append the "More..." menu:
				var menuMore = document.createElement('menu');
				menuMore.setAttribute('label', this.getStringBundle().getString('contextMenuMore'));
				menuMore.appendChild(menuPopupMore);
				menupopup.appendChild(menuMore);
			}
		}
		// Reset object to release used memory:
		this.fieldRules = null;
	},
	
	authenticateAndShowManualFillDialog: function(profileID) {
		try {
			Components.classes['@mozilla.org/security/pk11tokendb;1']
				.getService(Components.interfaces.nsIPK11TokenDB).getInternalKeyToken().login(false);

			var prompts = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
									.getService(Components.interfaces.nsIPromptService);
			// The selection and the subselection lists:
			var list = new Array();
			var listMore;
			// Hashs mapping the list positions to the original indices:
			var listIndexMapping = new Object();;
			var listMoreIndexMapping;
			for(var i=0; i < this.getFieldRules(profileID).length; i++) {
				if(this.getFieldRules(profileID)[i]['fieldRuleEnabled']) {
					list.push(this.getFieldRules(profileID)[i]['fieldRuleName']);
					listIndexMapping[list.length-1] = i;
				} else {
					// Add disabled items to a "More..." list:
					if(!listMore) {
						listMore = new Array();
						listMoreIndexMapping = new Object();
					}
					listMore.push(this.getFieldRules(profileID)[i]['fieldRuleName']);
					listMoreIndexMapping[listMore.length-1] = i;
				}
			}
			if(listMore) {
				// If all field rules of this profile are disabled, there is no need of a sublist:
				if(!list.length) {
					list = listMore;
					listIndexMapping = listMoreIndexMapping;
					listMore = null;
					listMoreIndexMapping = null;
				} else {
					list.push(this.getStringBundle().getString('contextMenuMore'));					
				}
			}
			var selected = {};
			var ok = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
						.getService(Components.interfaces.nsIPromptService)
						.select(
							window,
							null, // Window title - defaults to locale version of "Select"
							null, // Prompt text - defaults to empty string
							list.length,
							list,
							selected
						);
			if(ok) {
				// If "More..." is selected, show the disabled items as selection list:
				if(listMore && selected.value == list.length-1) {
					selected = {};
					ok = Components.classes['@mozilla.org/embedcomp/prompt-service;1']
						.getService(Components.interfaces.nsIPromptService)
						.select(
							window,
							null, // Window title - defaults to locale version of "Select"
							null, // Prompt text - defaults to empty string
							listMore.length,
							listMore,
							selected
						);
					if(ok) {
						this.fillTargetFormField(
							profileID,
							listMoreIndexMapping[selected.value]
						);
					}
				} else {
					this.fillTargetFormField(
						profileID,
						listIndexMapping[selected.value]
					);
				}
			}
		} catch(e) {
			// Authentication with master security device failed
		}
		// Reset object to release used memory:
		this.fieldRules = null;
	},
	
	fillTargetFormField: function(profileID, ruleID) {
		if(this.targetFormField) {
			var value = this.getFieldRules(profileID)[ruleID]['fieldRuleValue'];
			// Replace dynamic tags if enabled:
			if(this.autofillFormsPrefs.getBoolPref('enableDynamicTags')) {
				value = this.replaceDynamicTags(value);
			}
			try {
				// Try to use selection information:
				var newCursorPos = this.targetFormField.selectionStart + value.length;			
				this.targetFormField.value = 	this.targetFormField.value.substr(0, this.targetFormField.selectionStart)
												+ value
												+ this.targetFormField.value.substr(this.targetFormField.selectionEnd);	
				// Adjust the cursor position:
				this.targetFormField.selectionEnd = newCursorPos;
				this.targetFormField.selectionStart = newCursorPos;
			} catch(e) {
				// This input field does not support selections - just try to set the value:
				try {
					this.targetFormField.value = value;
				} catch(e) {
					// Catch errors if value could not be set on the form field
				}
			}
			// Reset objects to release used memory:
			this.fieldRules = null;
			this.dynamicTags = null;
			this.dynamicTagCodes = null;
		}
	},
	
	tooltip: function(event) {
		if (!document.tooltipNode) {
			return;
		}		
		// Get the tooltip node:
		var tooltip = document.getElementById('autofillFormsTooltip');
		if(tooltip) {
			// Add the associated tooltip content for each toolbar button menu item, toolbar button and statusbar icon:
			if(document.tooltipNode.id == 'autofillFormsButton' || document.tooltipNode.id == 'autofillFormsPanelIcon') {
				if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
					// Always retrieve the profile labels from file if useConfigDirectory is enabled:
					this.profileLabels = null;
					this.tooltipCurrentProfile = null;
				}				
				if(!this.tooltipCurrentProfile || !this.tooltipGrid) {
					// Remove all children nodes:
					while(tooltip.hasChildNodes()) {
						tooltip.removeChild(tooltip.firstChild);
					}
					// Add the current profile label:
					tooltip.appendChild(this.getTooltipCurrentProfile());
					// Add the tooltip grid with the command labels, mouse buttons and keyboard shortcuts:
					tooltip.appendChild(this.getTooltipGrid());
				}
				else
				{
					// Replace the first child (The current profile)
					tooltip.firstChild.setAttribute('value', this.getTooltipCurrentProfile());
				}
			} else {
				// Don't show tooltips for the toolbar button menu items:
				event.preventDefault();
			}
		}
	},

	getTooltipCurrentProfile: function() {
		var label;
		if(!this.tooltipCurrentProfile) {
			// Build an hbox for the first line of the tip box.
			var hbox = document.createElement('hbox');
			hbox.setAttribute(
				'id',
				'autofillFormsTooltipCurrentProfile'
			);			
			label = document.createElement('label');
			label.setAttribute(
				'value',
				this.getStringBundle().getString('currentProfileLabel')
			);
			label.setAttribute(
				'id',
				'autofillFormsTooltipCurrentProfileCaption'
			);
			hbox.appendChild(label);
			label = label.cloneNode(false);
			label.setAttribute(
				'id',
				'autofillFormsTooltipCurrentProfileLabel'
			);
			hbox.appendChild(label);

			var spacer = document.createElement('spacer');
			spacer.setAttribute('flex','1');
			hbox.appendChild(spacer);

			label = label.cloneNode(false);
			label.setAttribute(
				'id',
				'autofillFormsTooltipCurrentProfileSelection'
			);
			hbox.appendChild(label);
			this.tooltipCurrentProfile = hbox;
		}

		// Change the value of middle label (profile label)
		label = this.tooltipCurrentProfile.childNodes[1];

		var value = this.getProfileLabel(this.getProfileIndex());
		if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
			var doc = this.getDoc(this.getWin());
			if(doc && doc.forms && doc.forms.length > 0) {
				this.selectBestMatchingProfile(doc.location.href);
				value = this.getProfileLabel(this.getProfileIndex());
			}
		}
		
		this.myLog("Current profile :"+value);
		label.setAttribute('value', value);

		// Change the value of right label (selection type)
		label = this.tooltipCurrentProfile.childNodes[3];
		if(this.autofillFormsPrefs.getBoolPref('autoSelectBestProfile')) {
			label.setAttribute(
				'value',
				'('+this.getStringBundle().getString('profileSelectionAutomatic')+')'
			);
		} else {
			label.setAttribute(
				'value',
				'('+this.getStringBundle().getString('profileSelectionManual')+')'
			);
		}

		return this.tooltipCurrentProfile;
	},
	
	getTooltipGrid: function() {
		if(!this.tooltipGrid) {
			var commands = new Array();
			for(var property in this.shortcut) {
				commands.push(new Array(
					this.getStringBundle().getString('tooltip'+property.replace(/shortcut/,'')),
					this.getFormattedMouseButton(this.getMouseButton('mouseS'+property.substr(1))),
					this.getFormattedShortcut(this.getShortcut(property))
				));
			}
			var grid = document.createElement('grid');
			grid.setAttribute(
				'id',
				'autofillFormsTooltipGrid'
			);
			var columns = document.createElement('columns');
			var column = document.createElement('column');
			var rows = document.createElement('rows');
			var row = document.createElement('row');
			var label = document.createElement('label');
			columns.appendChild(column);
			columns.appendChild(column.cloneNode(false));
			columns.appendChild(column.cloneNode(false));
			grid.appendChild(columns);
			// Create the column headers:
			label.setAttribute(
				'class',
				'autofillFormsTooltipGridHeader'
			);
			label.setAttribute(
				'value',
				this.getStringBundle().getString('command')
			);
			row.appendChild(label);
			label = label.cloneNode(false);
			label.setAttribute(
				'value',
				this.getStringBundle().getString('mousebutton')
			);
			row.appendChild(label);
			label = label.cloneNode(false);
			label.setAttribute(
				'value',
				this.getStringBundle().getString('keyboardShortcut')
			);
			row.appendChild(label);
			rows.appendChild(row);
			// Create a row for each command:
			for(var i=0; i<commands.length; i++) {
				row = row.cloneNode(false);
				// Skip if neither mouseButton nor keyboard shortcut is set:
				if(!commands[i][1] && !commands[i][2]) {
					continue;
				}
				for(var j=0; j<commands[i].length; j++) {
					label = label.cloneNode(false);
					label.setAttribute(
						'value',
						commands[i][j]
					);
					if(j == 0) {
						label.setAttribute(
							'class',
							'autofillFormsTooltipGridCommand'
						);
					} else if(j == 1) {
						label.setAttribute(
							'class',
							'autofillFormsTooltipGridMouseButton'
						);
					} else {
						label.setAttribute(
							'class',
							'autofillFormsTooltipGridKeyboardShortcut'
						);
					}
					row.appendChild(label);
				}
				rows.appendChild(row);
			}
			grid.appendChild(rows);
			this.tooltipGrid = grid;
		}
		return this.tooltipGrid;
	},

	resetAllProfiles: function() {
		if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
			// Confirmation dialog:
			if(!this.getPrompts().confirm(
					null,
					this.getStringBundle().getString('resetAllProfilesTitle'),
					this.getStringBundle().getString('resetAllProfilesText')
				)
			) {
				return;	
			}
		}
		
		// Reset the user preferences:
		if(this.autofillFormsPrefs.prefHasUserValue('useConfigDirectory')) {
			this.autofillFormsPrefs.clearUserPref('useConfigDirectory');
		}
		if(this.autofillFormsPrefs.prefHasUserValue('storeEncrypted')) {
			this.autofillFormsPrefs.clearUserPref('storeEncrypted');
		}
		if(this.autofillFormsPrefs.prefHasUserValue('profileIndex')) {
			this.autofillFormsPrefs.clearUserPref('profileIndex');
		}
		if(this.autofillFormsPrefs.prefHasUserValue('profileLabels')) {
			this.autofillFormsPrefs.clearUserPref('profileLabels');
		}
		if(this.autofillFormsPrefs.prefHasUserValue('profileSiteRules')) {
			this.autofillFormsPrefs.clearUserPref('profileSiteRules');
		}
		if(this.autofillFormsPrefs.prefHasUserValue('fieldRules')) {
			this.autofillFormsPrefs.clearUserPref('fieldRules');
		}

		this.profileIndex = null;
		this.profileLabels = null;
		this.profileSiteRules = null;
		this.fieldRules = null;
		
		// Re-init the profiles lists:
		this.initProfilesLists();
		// Re-init the fieldRules tree:
		this.initTree();
		// Re-initialize the simple interface:
		this.initSimpleInterface();
		
		if(this.tree && this.selection) {
			try {
				// Clear out the fieldRules tree selections
				this.selection.select(-1);
			} catch(e) {
				this.log(e);
			}
		}

		//Re-init the config directory
		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			this.exportToConfigDirectory();
		}
	},
	
	initHelpUrlMenuList: function(event) {
		// Help URL dropdown menu
		this.helpUrlIndex = this.autofillFormsPrefs.getIntPref('helpUrlIndex');

		var helpUrlMenuList = document.getElementById('helpUrlMenuList');
		if(helpUrlMenuList) {

			helpUrlMenuList.removeAllItems();
			//Get the URLs preference string and split it using the '|' character:
			var urlArray = this.autofillFormsPrefs.getCharPref('helpUrl').split('|');
			for(var i=0; i < urlArray.length; i+=2) {
				helpUrlMenuList.appendItem(
					urlArray[i]
				);
			}
			helpUrlMenuList.selectedIndex = this.helpUrlIndex;
		}
	},

	initProfilesLists: function(event) {
		// The profiles tree:
		this.initProfilesTree();
		
		// Editable profiles menu list:
		var profilesMenuList = document.getElementById('profilesMenuList');
		if(profilesMenuList) {
			profilesMenuList.removeAllItems();
			for(var i=0; i < this.getProfileLabels().length; i++) {
				profilesMenuList.appendItem(
					this.getProfileLabel(i)
				);
			}
			profilesMenuList.selectedIndex = this.getProfileIndex();
		}
		// Simple interface profiles menu list:
		var simpleInterfaceProfileMenuList = document.getElementById('simpleInterfaceProfileMenuList');
		if(simpleInterfaceProfileMenuList) {
			simpleInterfaceProfileMenuList.removeAllItems();
			for(var i=0; i < this.getProfileLabels().length; i++) {
				simpleInterfaceProfileMenuList.appendItem(
					this.getProfileLabel(i)
				);
			}
			simpleInterfaceProfileMenuList.selectedIndex = this.getProfileIndex();
		}
		// Global profile selection:
		var globalProfileMenuList = document.getElementById('globalProfileMenuList');
		if(globalProfileMenuList) {
			globalProfileMenuList.removeAllItems();
			for(var i=0; i < this.getProfileLabels().length; i++) {
				globalProfileMenuList.appendItem(
					this.getProfileLabel(i)
				);
			}
			globalProfileMenuList.selectedIndex = this.getGlobalProfileIndex();
		}
		// Form fields context menu selection:
		var contextMenuProfileMenuList = document.getElementById('contextMenuProfileMenuList');
		if(contextMenuProfileMenuList) {
			// The first 3 items are "All profiles", "Active profile" and a menuseparator:
			try {
				while(contextMenuProfileMenuList.firstChild.childNodes[3]) {
					// The more convenient getItemAtIndex does not seem to work with Firefox versions < 3,
					// so we use DOM methods on the menupopup child node of the menu node instead:
					contextMenuProfileMenuList.firstChild.removeChild(
						contextMenuProfileMenuList.firstChild.childNodes[3]
					);
				}
			} catch(e) {
				// EZ need FIXME: "Warning: reference to undefined property contextMenuProfileMenuList.firstChild.childNodes[3]"
				this.myLog("No children...");
			}

			for(var i=0; i < this.getProfileLabels().length; i++) {
				contextMenuProfileMenuList.appendItem(
					this.getProfileLabel(i)
				);
			}
			contextMenuProfileMenuList.selectedIndex
				= this.getFormFieldsContextMenuProfileIndex()+3;
		}
		
		// The profile site rule textbox:
		this.initProfileSiteRuleTextBox();
	},
	
	updateProfilesLists: function() {
		// The more convenient getItemAtIndex does not seem to work with Firefox versions < 3,
		// so we use DOM methods on the menupopup child node of the menu nodes instead:
		
		// Editable profiles menu list:
		var profilesMenuList = document.getElementById('profilesMenuList');
		if(profilesMenuList) {
			profilesMenuList
				.firstChild.childNodes[this.getProfileIndex()].label
				= this.getProfileLabel(this.getProfileIndex());
		}
		// Simple interface profiles menu list:
		var simpleInterfaceProfileMenuList = document.getElementById('simpleInterfaceProfileMenuList');
		if(simpleInterfaceProfileMenuList) {
			simpleInterfaceProfileMenuList
				.firstChild.childNodes[this.getProfileIndex()].label
				= this.getProfileLabel(this.getProfileIndex());
		}
		// Global profile selection:
		var globalProfileMenuList = document.getElementById('globalProfileMenuList');
		if(globalProfileMenuList) {
			globalProfileMenuList
				.firstChild.childNodes[this.getProfileIndex()].label
				= this.getProfileLabel(this.getProfileIndex());
		}
		// Form fields context menu selection:
		var contextMenuProfileMenuList = document.getElementById('contextMenuProfileMenuList');
		if(contextMenuProfileMenuList) {
			// The first 3 items are "All profiles", "Active profile" and a menuseparator:
			contextMenuProfileMenuList
				.firstChild.childNodes[this.getProfileIndex()+3].label
				= this.getProfileLabel(this.getProfileIndex());
		}
		// The profiles tree:
		if(this.profilesTreeBox) {
			this.profilesTreeBox.invalidateRow(this.getProfileIndex());
		}
	},
	
	setHelpUrlIndex: function(index) {
		if(this.helpUrlIndex == index)
			return;

		this.helpUrlIndex = parseInt(index);
		this.autofillFormsPrefs.setIntPref('helpUrlIndex', this.helpUrlIndex);
			
	},

	getProfileIndex: function() {
		if(this.profileIndex == null)
			this.profileIndex = this.autofillFormsPrefs.getIntPref('profileIndex');
		return this.profileIndex;
	},
	
	setProfileIndex: function(index) {
		if(this.profileIndex == index)
			return;
			
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;
		
		this.autofillFormsPrefs.setIntPref('profileIndex',parseInt(index));
		// Update the tree view if present:
		if(this.tree) {
			// The settings page doesn't observe preferences changes - set the profileIndex manually:
			this.profileIndex = index;
			// Re-init the tree:
			this.initTree();
			// Re-initialize the simple interface:
			this.initSimpleInterface();
		}
		// Update the profiles tree selection if present and not already updated:
		if(this.profilesTree && this.profilesSelection.currentIndex != index) {
			// Select the current profile:
			this.profilesSelection.select(index);
	
			// Ensure row is visible (scrolls if not):
			this.profilesTreeBox.ensureRowIsVisible(index);
		}
		// Editable profiles menu list:
		var profilesMenuList = document.getElementById('profilesMenuList');
		if(profilesMenuList) {
			profilesMenuList.selectedIndex = this.getProfileIndex();
		}
		// Simple interface profiles menu list:
		var simpleInterfaceProfileMenuList = document.getElementById('simpleInterfaceProfileMenuList');
		if(simpleInterfaceProfileMenuList) {
			simpleInterfaceProfileMenuList.selectedIndex = this.getProfileIndex();
		}
		
		// The profile site rule textbox:
		this.initProfileSiteRuleTextBox();
	},

	getGlobalProfileIndex: function() {
		if(this.globalProfileIndex == null) {
			this.globalProfileIndex = this.autofillFormsPrefs.getIntPref('globalProfileIndex');
		}
		return this.globalProfileIndex;
	},
	
	setGlobalProfileIndex: function(index) {
		if(this.globalProfileIndex == index) {
			return;
		}
		this.autofillFormsPrefs.setIntPref('globalProfileIndex',parseInt(index));
		// The settings page doesn't observe preferences changes - set the profileIndex manually:
		this.globalProfileIndex = index;
	},

	getFormFieldsContextMenuProfileIndex: function() {
		if(this.formFieldsContextMenuProfileIndex == null) {
			this.formFieldsContextMenuProfileIndex
				= this.autofillFormsPrefs.getIntPref('formFieldsContextMenuProfileIndex');
		}
		return this.formFieldsContextMenuProfileIndex;
	},
	
	setFormFieldsContextMenuProfileIndex: function(index) {
		if(this.formFieldsContextMenuProfileIndex == index) {
			return;
		}
		this.autofillFormsPrefs.setIntPref('formFieldsContextMenuProfileIndex',parseInt(index));
		// The settings page doesn't observe preferences changes - set the profileIndex manually:
		this.formFieldsContextMenuProfileIndex = index;
	},

	getProfileLabelsFile: function() {
		var file = this.getConfigDirectory();
		file.append('profileLabels.txt');
		if(!file.exists()) {
			file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
		}
		return file;
	},

	exportProfileLabelsToConfigDirectory: function() {
		var prefString;
		// Get the profileLabels string from the preferences:
		prefString = this.autofillFormsPrefs
							.getComplexValue('profileLabels',Components.interfaces.nsIPrefLocalizedString)
							.data;
		if(prefString) {
			this.setFileContent(this.getProfileLabelsFile(), prefString);
		}
	},
	
	importProfileLabelsFromConfigDirectory: function() {
		var prefString;
		prefString = this.getFileContent(this.getProfileLabelsFile());
		if(prefString) {
			// Store the profileLabels as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'profileLabels',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},
	
	backupProfileLabels: function() {
		// Get the profileLabels string from the preferences:
		return this.autofillFormsPrefs
							.getComplexValue('profileLabels',Components.interfaces.nsISupportsString)
							.data;
	},

	restoreProfileLabels: function() {
		if(this.cachedProfileLabels) {
			// Store the profileLabels as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'profileLabels',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(this.cachedProfileLabels)
			);
		}
	},
	
	getProfileLabels: function() {
		if(this.profileLabels == null) {
			var prefString;
			if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
				// Get the profileLabels string from the profileLabels file in the configDirectory:
				prefString = this.getFileContent(this.getProfileLabelsFile());
			}
			if(!prefString) {
				prefString = this.autofillFormsPrefs
								.getComplexValue('profileLabels',Components.interfaces.nsIPrefLocalizedString)
								.data;
			}
			// The profile labels are stored as a string with tabs as separators:
			this.profileLabels = prefString.split('\t');
		}
		return this.profileLabels;
	},
	
	setProfileLabels: function(profileLabels) {
		// Save the profile labels separated by tabs:
		var prefString = profileLabels.join('\t');
		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			this.setFileContent(this.getProfileLabelsFile(), prefString);
		} else {
			this.autofillFormsPrefs.setComplexValue(
				'profileLabels',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},

	getProfileLabel: function(index) {
		while(this.getProfileLabels().length <= index) {
			this.getProfileLabels().push(this.getUniqueProfileLabel());
		}
		return this.getProfileLabels()[index];
	},
	
	setProfileLabel: function(index, label) {
		while(this.getProfileLabels().length <= index) {
			this.getProfileLabels().push(this.getUniqueProfileLabel());
		}
		this.getProfileLabels()[index] = label;
		// Save the profileLabels list in the preferences:
		this.setProfileLabels(this.getProfileLabels());
	},
	
	getUniqueProfileLabel: function(profileLabel,profileIndex) {
		if(!profileLabel) {
			profileLabel = 'Profile';
		}
		
		//remove the profileIndex label from the list (if present)
		var profileLabels = this.getProfileLabels().slice();
		if(profileIndex != null) {
			profileLabels.splice(profileIndex,1);
		}

		// Make sure the profile label is unique:
		if(!this.inArray(profileLabels, profileLabel)) {
			return profileLabel;
		}
		var i = profileLabel.lastIndexOf(' ');
		var n = parseInt(profileLabel.substr(i+2));
		if(isNaN(n)) {
			return this.getUniqueProfileLabel(profileLabel+' (2)',profileIndex);
		}
		n++;
		profileLabel = profileLabel.substr(0, i)+' ('+n+')';
		return this.getUniqueProfileLabel(profileLabel,profileIndex);
	},
	
	changeProfileLabel: function(newProfileLabel) {
		var profilesMenuList = document.getElementById('profilesMenuList');
		if(profilesMenuList) {
			// Make sure the new profile label is safe and unique:
			newProfileLabel = this.getUniqueProfileLabel(this.makeSafe(newProfileLabel),this.getProfileIndex());
			// Update the label of the selected profile:
			this.setProfileLabel(this.getProfileIndex(), newProfileLabel);
			// Update the profiles textbox contents:
			profilesMenuList.inputField.value = newProfileLabel;
			document.getElementById('profileLabelTextBox').value = newProfileLabel;
			// Update the profiles lists:
			this.updateProfilesLists();
		}
	},

	initProfileSiteRuleTextBox: function(event) {
		var profileSiteRuleTextBox = document.getElementById('profileSiteRuleTextBox');
		if(profileSiteRuleTextBox) {
			profileSiteRuleTextBox.value = this.getProfileSiteRule(this.getProfileIndex());
		}
	},
	
	changeProfileSiteRule: function(siteRule) {
		var profileSiteRuleTextBox = document.getElementById('profileSiteRuleTextBox');
		if(profileSiteRuleTextBox) {
			// Check the regular expression before updating the profile site rules:
			try {
				siteRule = this.getRegExpStr(
					this.makeSafe(siteRule)
				);
				profileSiteRuleTextBox.value = siteRule;
				document.getElementById('profileSiteRuleTextBox2').value = siteRule;
				
				var newProfileSiteRules = this.getProfileSiteRules();
				newProfileSiteRules[this.getProfileIndex()] = siteRule;
				this.setProfileSiteRules(newProfileSiteRules);
				
				// Update the profiles tree:
				if(this.profilesTreeBox) {
					this.profilesTreeBox.invalidateRow(this.getProfileIndex());
				}
			} catch(e) {
				this.invalidRegExpAlert(e);
			}			
		}
	},

	getProfileSiteRulesFile: function() {
		var file = this.getConfigDirectory();
		file.append('profileSiteRules.txt');
		if(!file.exists()) {
			file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
		}
		return file;
	},

	exportProfileSiteRulesToConfigDirectory: function() {
		var prefString;
		// Get the profileSiteRules string from the preferences:
		prefString = this.autofillFormsPrefs
							.getComplexValue('profileSiteRules',Components.interfaces.nsISupportsString)
							.data;
		if(prefString) {
			this.setFileContent(this.getProfileSiteRulesFile(), prefString);
		}
	},
	
	importProfileSiteRulesFromConfigDirectory: function() {
		var prefString;
		prefString = this.getFileContent(this.getProfileSiteRulesFile());
		if(prefString) {
			// Store the profileSiteRules as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'profileSiteRules',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},
	
	backupProfileSiteRules: function() {
		// Get the profileSiteRules string from the preferences:
		return this.autofillFormsPrefs
							.getComplexValue('profileSiteRules',Components.interfaces.nsISupportsString)
							.data;
	},

	restoreProfileSiteRules: function() {
		if(this.cachedProfileSiteRules) {
			// Store the profileSiteRules as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'profileSiteRules',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(this.cachedProfileSiteRules)
			);
		}
	},
	
	getProfileSiteRules: function() {
		if(this.profileSiteRules == null) {
			var prefString;
			if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
				// Get the profileSiteRules string from the profileSiteRules file in the configDirectory:
				prefString = this.getFileContent(this.getProfileSiteRulesFile());
			}
			if(!prefString) {
				prefString = this.autofillFormsPrefs
								.getComplexValue('profileSiteRules',Components.interfaces.nsISupportsString)
								.data;
			}
			// The profile SiteRules are stored as a string with tabs as separators:
			this.profileSiteRules = prefString.split('\t');
		}
		return this.profileSiteRules;
	},
	
	setProfileSiteRules: function(profileSiteRules) {
		// Save the profile SiteRules separated by tabs:
		var prefString = profileSiteRules.join('\t');
		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			this.setFileContent(this.getProfileSiteRulesFile(), prefString);
		} else {
			this.autofillFormsPrefs.setComplexValue(
				'profileSiteRules',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},

	getProfileSiteRule: function(index) {
		while(this.getProfileSiteRules().length <= index) {
			this.getProfileSiteRules().push('(?:)');
		}
		return this.getProfileSiteRules()[index];
	},

	setProfileSiteRule: function(index, siteRule) {
		while(this.getProfileSiteRules().length <= index) {
			this.getProfileSiteRules().push('(?:)');
		}
		this.getProfileSiteRules()[index] = siteRule;
		// Save the profileSiteRules in the preferences:
		this.setProfileSiteRules(this.getProfileSiteRules());
	},

	addFormAsProfile: function(event) {
		if(this.targetFormField && this.targetFormField.form) {		
			var elements = this.targetFormField.form.elements;
			var doc = this.targetFormField.form.ownerDocument;

			var newProfile = new Array();
			
			// Go through the form elements:
			for(var i=0; i<elements.length; i++) {			
				// Only use valid form fields:
				if(this.isValidFormField(elements[i])) {
					var value;
					var overwrite = true;

					// Create the fieldRule (from name, label or id):
					var fieldRule = this.getFieldRuleForElement(elements[i]);
					
					switch(elements[i].type) {
						case 'checkbox':
							// Add a rule to uncheck the checkbox if it is unchecked:
							if(!elements[i].checked) {
								overwrite = false;
							}
							value = this.getRegExpStrForValue(elements[i].value);
							break;
						case 'radio':
							// Only add checked radio buttons:
							if(!elements[i].checked) {
								continue;
							}
							value = this.getRegExpStrForValue(elements[i].value);
							break;
						case 'select-one':
							value = this.getRegExpStrForValue(elements[i].value);
							break;
						case 'select-multiple':
							var fieldRuleLabel = this.makeSafe(this.getFieldRuleNameForElement(elements[i]));
							// Add all options as fieldRules, set "overwrite" to true if selected:
							for(var j = 0; j < elements[i].options.length; j++) {
								newProfile.push(
									this.createFieldRule(
										fieldRuleLabel+' ('+j+')',
										this.getRegExpStrForValue(elements[i].options[j].value),
										fieldRule,
										'(?:)',
										elements[i].options[j].selected,
										true
									)
								);
							}
							continue;
						default:
							value = this.makeSafe(this.replaceControlCharacters(elements[i].value));
							break;
					}

					// Add the current element as new rule to the profile list:
					newProfile.push(
						this.createFieldRule(
							this.makeSafe(this.getFieldRuleNameForElement(elements[i])),
							value,
							fieldRule,
							'(?:)',
							overwrite,
							true
						)
					);
				}
			}

			// Initialize the fieldRules:
			this.getFieldRules();
			
			// Add the new profile to the fieldRules:
			this.fieldRules.push(newProfile);
			// Save the profiles in the preferences:
			this.setFieldRules();
		
			// Add a label for default empty profile:
			if(this.getProfileLabels().length == 0) {
				this.getProfileLabels().push(this.getUniqueProfileLabel());
			}		
			// Use the documents hostname as profile label and add it to the profile labels list:
			this.getProfileLabels().push(this.getUniqueProfileLabel(this.makeSafe(doc.location.host)));
			// Save the profileLabels list in the preferences:
			this.setProfileLabels(this.getProfileLabels());

			// Use the protocol and domain of the web form as profile siteRule:
			this.getProfileSiteRules().push(this.getSiteRuleForURL(doc.location.protocol+'//'+doc.location.host));
			// Save the profileSiteRules in the preferences:
			this.setProfileSiteRules(this.getProfileSiteRules());
			
			// Save the the new profile index as selected profileIndex:
			this.setProfileIndex(this.getProfileLabels().length-1);

			// Reset the target form field:
			this.targetFormField = null;

			// Create parameters for the settings page:
			var params = new Object();
			params.newProfileFromForm = true;
			
			// Open up the settings page:
			this.showDialog('chrome://autofillForms/content/autofillFormsOptions.xul', params);
		}
	},
	
	addProfile: function(newProfileLabel) {
		// Duplicate the selected profile (do a deep copy):
		this.fieldRules.push(
			this.copyFieldRules(this.getFieldRules())
		);
		// Save the profiles in the preferences:
		this.setFieldRules();
		// Add profile label for default empty profile:
		if(this.getProfileLabels().length == 0) {
			this.getProfileLabels().push(this.getUniqueProfileLabel());
		}
		// Add the (unique) newProfileLabel to the profileLabels list:
		this.getProfileLabels().push(this.getUniqueProfileLabel(this.makeSafe(newProfileLabel)));
		// Save the profileLabels list in the preferences:
		this.setProfileLabels(this.getProfileLabels());
		// Add a new empty profileSiteRule:
		this.getProfileSiteRules().push('(?:)');
		// Save the profileSiteRules in the preferences:
		this.setProfileSiteRules(this.getProfileSiteRules());
		// Save the the new profile index as selected profileIndex:
		this.setProfileIndex(this.getProfileLabels().length-1);
		// Update the profiles lists:
		this.initProfilesLists();
		// Re-init the fieldRules tree:
		this.initTree();
		// Re-initialize the simple interface:
		this.initSimpleInterface();
	},
	
	removeProfile: function(event) {
		if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
			// Confirmation dialog:
			if(!this.getPrompts().confirm(
					null,
					this.getStringBundle().getString('removeProfileTitle'),
					this.getStringBundle().getString('removeProfileText')
				)
			) {
				return;	
			}
		}
		
		// Remove the selected profile from the list:
		this.fieldRules.splice(this.getProfileIndex(),1);
		// Save the profiles in the preferences:
		this.setFieldRules();
		// Remove the selected profile from the profileLabels list:
		this.getProfileLabels().splice(this.getProfileIndex(),1);
		// Save the profileLabels list in the preferences:
		this.setProfileLabels(this.getProfileLabels());
		// Remove the selected profile's siteRule:
		this.getProfileSiteRules().splice(this.getProfileIndex(),1);
		// Save the profileSiteRules in the preferences:
		this.setProfileSiteRules(this.getProfileSiteRules());
		// Adjust the profileIndex if the last profile on the list has been deleted:
		if(this.getProfileIndex()+1 > this.fieldRules.length) {
			var newIndex = this.fieldRules.length>0 ? this.fieldRules.length-1 : 0;
			this.setProfileIndex(newIndex);
		}
		// Update the profiles lists:
		this.initProfilesLists();
		// Re-init the tree:
		this.initTree();
		// Re-initialize the simple interface:
		this.initSimpleInterface();
	},
	
	createFieldRule: function(name,value,fieldRule,siteRule,overwrite,enabled) {
		var rule = new Object();
		rule['fieldRuleName'] = name;
		rule['fieldRuleValue'] = value;
		rule['fieldRuleFieldRule'] = fieldRule;
		rule['fieldRuleSiteRule'] = siteRule;
		rule['fieldRuleOverwrite'] = overwrite;
		rule['fieldRuleEnabled'] = enabled;
		return rule;
	},

	getRegExpPasswordLabel: function() {
		if(!arguments.callee.regExpPass) {
			arguments.callee.regExpPass = new RegExp(
								this.autofillFormsPrefs
								.getComplexValue('regExpPasswordLabel',Components.interfaces.nsIPrefLocalizedString)
								.data,
								'i');
		}
		return arguments.callee.regExpPass;
	},
	
	initTree: function() {	
		// Get the tree:
		this.tree = document.getElementById('fieldRulesTree');
		
		if(this.tree) {
		
			// Implement the TreeView interface:
			this.treeView = {
				rowCount: 0,
				setTree: function(tree){},
				getImageSrc: function(row,column) {},
				getProgressMode: function(row,column) {},
				getCellValue: function(row,column) {
					var rowObj = this.parent.getFieldRules()[row];
					if(rowObj) {
						return rowObj[column.id];
					}
					return null;
				},
				getCellText: function(row,column){
					var rowObj = this.parent.getFieldRules()[row];
					if(rowObj) {
						if(column.id=='fieldRuleValue' &&
							this.parent.getRegExpPasswordLabel().test(rowObj['fieldRuleName'])) {
							// Show passwords as asterisks:
							return rowObj[column.id].replace(/./g, '*');
						} else {
							return rowObj[column.id];
						}
					}
					return '';
				},
				isEditable: function(row,column){
					// Only checkbox columns are editable:
					if(column.id=='fieldRuleOverwrite' || column.id=='fieldRuleEnabled')
						return true;
					else
						return false;
				},
				setCellValue: function(row,column,value){
					var rowObj = this.parent.getFieldRules()[row];
					if(rowObj) {
						rowObj[column.id] = value;
						// Notify the tree:
						this.parent.treeBox.invalidateRow(row);
						// Update the preferences:
						this.parent.setFieldRules();
						// Update the simple interface (add/remove enabled/disabled rules):
						if(column.id=='fieldRuleEnabled') {
							if(value == 'true') {
								this.parent.addSimpleInterfaceRow(row);
							} else {
								this.parent.removeSimpleInterfaceRow(row);
							}
						}	
					}
				},
				isSeparator: function(index) {return false;},
				isSorted: function() {return false;},
				isContainer: function(index) {return false;},
				cycleHeader: function(column) {},
				getRowProperties: function(row,prop){},
				getColumnProperties: function(column,prop){},
				getCellProperties: function(row,column,prop){},
				getParentIndex: function(index) {return -1}
			};
			// Set the autofillForms object as parent:
			this.treeView.parent = this;
			
			// Set the tree length using the fieldRules list length:
			this.treeView.rowCount = this.getFieldRules().length;
					
			// Assign the treeview:
			this.tree.view = this.treeView;
			
			// The TreeSelection object:
			this.selection = this.tree.view.selection;
			
			// The TreeBox object:
			this.treeBox = this.tree.treeBoxObject;		
		}
	},
		
	sortFieldRules: function(event) {
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;

		if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
			// Confirmation dialog:
			if(!this.getPrompts().confirm(
					null,
					this.getStringBundle().getString('sortFieldRulesTitle'),
					this.getStringBundle().getString('sortFieldRulesText')
				)
			) {
				return;	
			}
		}	

		// Get the id of the column:
		var id = event.target.id;
		
		// Helper function to sort the fieldRules objects:
		function customSort(a,b) {
			// This enables comparison of boolean true and false:
			var x = a[id].toString();
			var y = b[id].toString();

			if(x > y) return 1;
			if(x < y) return -1;
			return 0;
		}
		
		// Sort the form field rules using the helper function:
		this.getFieldRules().sort(customSort);
		
		// Change sort direction for next click:
		if(this.ascending) {
			this.ascending = false;
		} else {
			this.getFieldRules().reverse();
			this.ascending = true;
		}
		
		// Notify the tree:
		this.treeBox.invalidate();
		
		// Clear out selections
		this.selection.select(-1);
		
		// Update the preferences:
		this.setFieldRules();

		// Re-initialize the simple interface:
		this.initSimpleInterface();
	},
	
	selectedFieldRule: function(event) {
		if(this.selection.currentIndex == -1) {
			// Disable buttons:
			document.getElementById('buttonRemoveFieldRule').setAttribute('disabled', 'true');
			document.getElementById('buttonMoveUpFieldRule').setAttribute('disabled', 'true');
			document.getElementById('buttonMoveDownFieldRule').setAttribute('disabled', 'true');
			
			this.lastSelectedIndex = null;
		} else if(this.selection.count == 1) {
			// The onchange event (as well as onblur, etc.) of the textboxes seems to be ignored if a new item is selected,
			// so we try and apply the field rules of the last element (if changed):
			if(this.lastSelectedIndex !== null) {
				this.applyFieldRuleOnIndex(this.lastSelectedIndex);
			}
			
			// Update the textboxes with the selected fieldRule:
			var index = this.selection.currentIndex;
			document.getElementById('fieldRuleNameTextBox').value = this.getFieldRules()[index]['fieldRuleName'];
			document.getElementById('fieldRuleValueTextBox').value = this.getFieldRules()[index]['fieldRuleValue'];
			document.getElementById('fieldRuleFieldRuleTextBox').value = this.getFieldRules()[index]['fieldRuleFieldRule'];
			document.getElementById('fieldRuleSiteRuleTextBox').value = this.getFieldRules()[index]['fieldRuleSiteRule'];
			
			// Enable/Disable buttons:
			document.getElementById('buttonRemoveFieldRule').setAttribute('disabled', 'false');
			document.getElementById('buttonMoveUpFieldRule').setAttribute(
				'disabled',
				(index == 0)
			);
			document.getElementById('buttonMoveDownFieldRule').setAttribute(
				'disabled',
				(index == this.getFieldRules().length-1)
			);
			
			// Save the last selected index and reset it to null for any other action than just a single selection:
			this.lastSelectedIndex = index;
		} else if(this.selection.count > 1) {
			// Enable/Disable buttons:
			document.getElementById('buttonRemoveFieldRule').setAttribute('disabled', 'false');
			document.getElementById('buttonMoveUpFieldRule').setAttribute('disabled', 'true');
			document.getElementById('buttonMoveDownFieldRule').setAttribute('disabled', 'true');
			
			this.lastSelectedIndex = null;
		}
	},

	initProfilesTree: function() {
		this.profilesTree = document.getElementById('profilesTree');
		if(this.profilesTree) {

			// Implement the profiles TreeView interface:
			this.profilesTreeView = {
				rowCount: 0,
				setTree: function(tree){},
				getImageSrc: function(row,column) {},
				getProgressMode: function(row,column) {},
				getCellValue: function(row,column) {
					if(column.id=='profilesTreeColName') {
						return this.parent.getProfileLabel(row);
					} else {
						return this.parent.getProfileSiteRule(row);
					}
				},
				getCellText: function(row,column){
					if(column.id=='profilesTreeColName') {
						return this.parent.getProfileLabel(row);
					} else {
						return this.parent.getProfileSiteRule(row);
					}
				},
				isEditable: function(row,column){return false;},
				setCellValue: function(row,column,value){},
				isSeparator: function(index) {return false;},
				isSorted: function() {return false;},
				isContainer: function(index) {return false;},
				cycleHeader: function(column) {},
				getRowProperties: function(row,prop){},
				getColumnProperties: function(column,prop){},
				getCellProperties: function(row,column,prop){},
				getParentIndex: function(index) {return -1}
			};
			// Set the autofillForms object as parent:
			this.profilesTreeView.parent = this;

			// Seems like we need to reset these arrays to have a consistens UI:
			this.profileLabels = null;
			this.profileSiteRules = null;
			
			// Set the tree length using the profiles labels list length:
			this.profilesTreeView.rowCount = this.getProfileLabels().length;
					
			// Assign the treeview:
			this.profilesTree.view = this.profilesTreeView;
			
			// The TreeSelection object:
			this.profilesSelection = this.profilesTree.view.selection;
			
			// The TreeBox object:
			this.profilesTreeBox = this.profilesTree.treeBoxObject;
	
			// Select the current profile:
			this.profilesSelection.select(this.getProfileIndex());
	
			// Ensure row is visible (scrolls if not):
			this.profilesTreeBox.ensureRowIsVisible(this.getProfileIndex());
		}
	},
	
	selectedProfile: function(event) {
		var index = this.profilesSelection.currentIndex;
		if(index != -1) {
			this.setProfileIndex(index);
			
			if(index > 0) {
				document.getElementById('buttonMoveUpProfile').disabled = false;
			} else {
				document.getElementById('buttonMoveUpProfile').disabled = true;
			}
			if(index+1 < this.getProfileLabels().length) {
				document.getElementById('buttonMoveDownProfile').disabled = false;
			} else {
				document.getElementById('buttonMoveDownProfile').disabled = true;
			}
			
			if(document.getElementById('profileLabelTextBox')) {
				document.getElementById('profileLabelTextBox').value = this.getProfileLabel(this.getProfileIndex());
			}
			if(document.getElementById('profileSiteRuleTextBox2')) {
				document.getElementById('profileSiteRuleTextBox2').value = this.getProfileSiteRule(this.getProfileIndex());
			}
		} else {
			document.getElementById('buttonMoveUpProfile').disabled = true;
			document.getElementById('buttonMoveDownProfile').disabled = true;
		}
	},
	
	moveUpProfile: function(event) {
		var tmpProfile = this.getFieldRules(this.getProfileIndex()-1);
		this.fieldRules[this.getProfileIndex()-1] = this.getFieldRules(this.getProfileIndex());
		this.fieldRules[this.getProfileIndex()] = tmpProfile;
		this.setFieldRules();
		
		var tmpProfileLabel = this.getProfileLabel(this.getProfileIndex()-1);
		this.getProfileLabels()[this.getProfileIndex()-1] = this.getProfileLabel(this.getProfileIndex());
		this.getProfileLabels()[this.getProfileIndex()] = tmpProfileLabel;
		this.setProfileLabels(this.getProfileLabels());
		
		var tmpProfileSiteRule = this.getProfileSiteRule(this.getProfileIndex()-1);
		this.getProfileSiteRules()[this.getProfileIndex()-1] = this.getProfileSiteRule(this.getProfileIndex());
		this.getProfileSiteRules()[this.getProfileIndex()] = tmpProfileSiteRule;
		this.setProfileSiteRules(this.getProfileSiteRules());

		this.setProfileIndex(this.getProfileIndex()-1);
		
		this.initProfilesLists();
	},
	
	moveDownProfile: function(event) {
		var tmpProfile = this.getFieldRules(this.getProfileIndex()+1);
		this.fieldRules[this.getProfileIndex()+1] = this.getFieldRules(this.getProfileIndex());
		this.fieldRules[this.getProfileIndex()] = tmpProfile;
		this.setFieldRules();
		
		var tmpProfileLabel = this.getProfileLabel(this.getProfileIndex()+1);
		this.getProfileLabels()[this.getProfileIndex()+1] = this.getProfileLabel(this.getProfileIndex());
		this.getProfileLabels()[this.getProfileIndex()] = tmpProfileLabel;
		this.setProfileLabels(this.getProfileLabels());
		
		var tmpProfileSiteRule = this.getProfileSiteRule(this.getProfileIndex()+1);
		this.getProfileSiteRules()[this.getProfileIndex()+1] = this.getProfileSiteRule(this.getProfileIndex());
		this.getProfileSiteRules()[this.getProfileIndex()] = tmpProfileSiteRule;
		this.setProfileSiteRules(this.getProfileSiteRules());

		this.setProfileIndex(this.getProfileIndex()+1);
		
		this.initProfilesLists();
	},
	
	sortProfiles: function(event) {
		var newSelectedIndex = this.getProfileIndex();
		var fieldArray = this.getFieldsArray();
		var oldIndex;

		switch(event.target.id) {
			case 'profilesTreeColName':
				//Sort by Profile Label
				fieldArray.sort(function (a, b) {
					if (a[1] == b[1]) {
						return 0;
					}
					if (a[1] < b[1]) {
						return -1;
					}
					return 1;
				});
				if (!this.profilesAscending) {
					fieldArray.reverse();
				}
				for(var i=0; i<this.getProfileLabels().length; i++) {
					oldIndex = fieldArray[i][0];
					if(oldIndex == this.getProfileIndex()) {
						newSelectedIndex = i;
					}
					this.getProfileLabels()[i] = fieldArray[i][1];
					this.getProfileSiteRules()[i] = fieldArray[i][2];
					this.fieldRules[i] = fieldArray[i][3];
				}
				break;
				
			case 'profilesTreeColSiteRule':
				//Sort by Profile Site Rule
				fieldArray.sort(function (a, b) {
					if (a[2] == b[2]) {
						return 0;
					}
					if (a[2] < b[2]) {
						return -1;
					}
					return 1;
				});
				if (!this.profilesAscending) {
					fieldArray.reverse();
				}
				for(var i=0; i<this.getProfileLabels().length; i++) {
					oldIndex = fieldArray[i][0];
					if(oldIndex == this.getProfileIndex()) {
						newSelectedIndex = i;
					}
					this.getProfileLabels()[i] = fieldArray[i][1];
					this.getProfileSiteRules()[i] = fieldArray[i][2];
					this.fieldRules[i] = fieldArray[i][3];
				}
				break;
		}
		// Change sort direction for next click:
		this.profilesAscending = !this.profilesAscending;
		
		this.setFieldRules();
		this.setProfileLabels(this.getProfileLabels());	
		this.setProfileSiteRules(this.getProfileSiteRules());
		
		this.setProfileIndex(newSelectedIndex);
		
		this.initProfilesLists();
	},
	getFieldsArray: function() {		
		// This creates an array of [i, ProfileLabel, ProfileSiteRule, fieldRules] rows
		// we can then sort by rows[1] or rows[2] and store the row elements back in
		// their respective arrays.
		var row;
		var fieldArray = new Array();
		var tmpProfileLabels = this.getProfileLabels().slice(0);
		var tmpProfileSiteRules = this.getProfileSiteRules().slice(0);

		for(var i=0; i<this.getProfileSiteRules().length; i++) {
			row = new Array();
			row.push(i);
			row.push(tmpProfileLabels[i]);
			row.push(tmpProfileSiteRules[i]);
			row.push(this.getFieldRules(i));
			fieldArray.push(row);
		}
		return fieldArray;
	},
	profilesTreeHandleKeyPress: function(event) {
		if(event.keyCode == 46) {
			this.removeProfile();
		}
	},
	
	invalidRegExpAlert: function(error) {
		// Invalid regular expression alert:
		this.getPrompts().alert(
			null,
			this.getStringBundle().getString('invalidRegExpTitle'),
			this.getStringBundle().getString('invalidRegExpText') + "\n\n" + error
		);
	},
	
	makeSafe: function(str) {
		// Remove all tabs and linefeeds from the given string
		// (these are used as separators):
		return str.replace(/\t|\n/g, '');
	},

	replaceControlCharacters: function(str) {
		return str.replace(
			/\n|\t/g,
			this.replaceControlCharactersCallback
		);
	},
	
	replaceControlCharactersCallback: function(str) {
		switch(str) {
			case "\n":
				return autofillForms.autofillFormsPrefs.getCharPref('placeholderLineBreak');
			case "\t":
				return '	';
			default:
				return str;
		}
	},

	replaceControlCharacterPlaceholders: function(str) {
		try {
			var regExpObj = new RegExp(
				'('
				+this.autofillFormsPrefs.getCharPref('placeholderLineBreak')
				+')|('
				+this.autofillFormsPrefs.getCharPref('placeholderTab')
				+')',
				'g'
			);
			return str.replace(
				regExpObj,
				this.replaceControlCharacterPlaceholdersCallback
			);
		} catch(e) {
			return str;
		}
	},
	
	replaceControlCharacterPlaceholdersCallback: function(str) {
		switch(str) {
			case autofillForms.autofillFormsPrefs.getCharPref('placeholderLineBreak'):
				return "\n";
			case autofillForms.autofillFormsPrefs.getCharPref('placeholderTab'):
				return "\t";
			default:
				return str;
		}
	},

	applyFieldRuleOnIndex: function(index) {
		// Check the regular expressions:
		try {
			var fieldRule = this.getRegExpStr(
				this.makeSafe(document.getElementById('fieldRuleFieldRuleTextBox').value)
			);
			document.getElementById('fieldRuleFieldRuleTextBox').value = fieldRule;
			
			var siteRule = this.getRegExpStr(
				this.makeSafe(document.getElementById('fieldRuleSiteRuleTextBox').value)
			);
			document.getElementById('fieldRuleSiteRuleTextBox').value = siteRule;
		} catch(e) {
			this.invalidRegExpAlert(e);
			return;
		}

		var ruleName = this.makeSafe(document.getElementById('fieldRuleNameTextBox').value);
		var ruleValue = this.makeSafe(document.getElementById('fieldRuleValueTextBox').value);

		if(	this.getFieldRules()[index] && (
			this.getFieldRules()[index]['fieldRuleName'] != ruleName ||
			this.getFieldRules()[index]['fieldRuleValue'] != ruleValue ||
			this.getFieldRules()[index]['fieldRuleFieldRule'] != fieldRule ||
			this.getFieldRules()[index]['fieldRuleSiteRule'] != siteRule)) {
			// Update the formFieldRule on the given index:
			this.getFieldRules()[index]['fieldRuleName'] = ruleName;
			this.getFieldRules()[index]['fieldRuleValue'] = ruleValue;
			this.getFieldRules()[index]['fieldRuleFieldRule'] = fieldRule;
			this.getFieldRules()[index]['fieldRuleSiteRule'] = siteRule;
	
			// Notify the tree:
			this.treeBox.invalidateRow(index);
			
			// Update the preferences:
			this.setFieldRules();	
			
			// Update the related row of the simple interface:
			this.updateSimpleInterfaceRow(index);
		}
	},
	
	applyFieldRule: function(event) {
		// Only apply changes if one item is selected:
		if(this.selection.count == 1) {
			// Update the selected formFieldRule:
			this.applyFieldRuleOnIndex(this.selection.currentIndex);
		}
	},
	
	addFieldRule: function(event) {
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;
		
		// Check the regular expressions:
		try {
			var fieldRuleFieldRuleTextBox = document.getElementById('fieldRuleFieldRuleTextBox');
			var fieldRule = this.getRegExpStr(
				this.makeSafe(fieldRuleFieldRuleTextBox.value)
			);
			fieldRuleFieldRuleTextBox.value = fieldRule;

			var fieldRuleSiteRuleTextBox = document.getElementById('fieldRuleSiteRuleTextBox');
			var siteRule = this.getRegExpStr(
				this.makeSafe(fieldRuleSiteRuleTextBox.value)
			);
			fieldRuleSiteRuleTextBox.value = siteRule;
		} catch(e) {
			this.invalidRegExpAlert(e);
			return;
		}
		
		var newFieldRule = 	this.createFieldRule(
			this.makeSafe(document.getElementById('fieldRuleNameTextBox').value),
			this.makeSafe(document.getElementById('fieldRuleValueTextBox').value),
			fieldRule,
			siteRule,
			true,
			true
		)
		
		var newFieldRuleIndex;
		
		// Add the new formFieldRule right after the selected position or to the start of the list:
		if(this.selection.currentIndex == -1 || this.selection.currentIndex == this.treeView.rowCount) {
			this.getFieldRules().unshift(newFieldRule);
			newFieldRuleIndex = 0;
		} else {
			newFieldRuleIndex = this.selection.currentIndex+1;
			this.getFieldRules().splice(
				newFieldRuleIndex,
				0,
				newFieldRule
			);
		}

		// Update the tree count and notify the tree:
		this.treeView.rowCount++;
		this.treeBox.rowCountChanged(this.treeView.rowCount, +1);
		this.treeBox.invalidate();
		
		// Select the new item:
		this.selection.select(newFieldRuleIndex);

		// Ensure row is visible (scrolls if not):
		this.treeBox.ensureRowIsVisible(newFieldRuleIndex);
		
		// Update the preferences:
		this.setFieldRules();

		// Re-initialize the simple interface:
		this.initSimpleInterface();
	},
	
	removeFieldRule: function(event) {
		this.removeSelectedFieldRules();
	},
	
	moveUpFieldRule: function(event) {
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;
		
		var index = this.selection.currentIndex;

		// Change place with the next upper item:
		var sibling = this.getFieldRules()[index-1];
		this.getFieldRules()[index-1] = this.getFieldRules()[index];
		this.getFieldRules()[index] = sibling;
		
		// Keep moved item selected:
		this.selection.select(index-1);
		
		// Notify the tree:
		this.treeBox.invalidate();
		
		// Ensure row is visible (scrolls if not):
		this.treeBox.ensureRowIsVisible(index-1);
		
		// Update the preferences:
		this.setFieldRules();

		// Update the related rows of the simple interface:
		this.updateSimpleInterfaceRow(index-1);
		this.updateSimpleInterfaceRow(index);
	},

	moveDownFieldRule: function(event) {
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;
		
		var index = this.selection.currentIndex;

		// Change place with the next lower item:
		var sibling = this.getFieldRules()[index+1];
		this.getFieldRules()[index+1] = this.getFieldRules()[index];
		this.getFieldRules()[index] = sibling;
		
		// Keep moved item selected:
		this.selection.select(index+1);
		
		// Notify the tree:
		this.treeBox.invalidate();

		// Ensure row is visible (scrolls if not):
		this.treeBox.ensureRowIsVisible(index+1);
		
		// Update the preferences:
		this.setFieldRules();

		// Update the related rows of the simple interface:
		this.updateSimpleInterfaceRow(index+1);
		this.updateSimpleInterfaceRow(index);
	},
		
	removeSelectedFieldRules: function(event) {
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;
		
		if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
			// Confirmation dialog:
			if(!this.getPrompts().confirm(
					null,
					this.getStringBundle().getString('removeFieldRulesTitle'),
					this.getStringBundle().getString('removeFieldRulesText')
				)
			) {
				return;	
			}
		}
	
		// Start of update batch:
		this.treeBox.beginUpdateBatch();
			
		// Helper object to store a range:
		function Range(start, end) {
			this.start = start.value;
			this.end = end.value;
		}
		
		// List of ranges:
		var ranges = new Array();
		
		// Get the number of ranges:
		var numRanges = this.selection.getRangeCount();
		
		// Helper vars to store the range end points:
		var start = new Object();
		var end = new Object();
		
		// We store the list of ranges first, as calling
		// this.treeBox.rowCountChanged()
		// seems to invalidate the current selection
		
		for(var i=0; i < numRanges; i++) {
			// Get the current range end points:
			this.selection.getRangeAt(i,start,end);
			// Store them as a Range object in the ranges list:
			ranges[i] = new Range(start, end);
		}
		
		for(var i=0; i < numRanges; i++) {
			// Go through the stored ranges:
			for(var j = ranges[i].start; j <= ranges[i].end; j++) {
				// Set the selected fieldRules to null:
				this.getFieldRules()[j] = null;
			}
			
			// Calculate the new tree count:
			var count = ranges[i].end - ranges[i].start + 1;
			
			// Update the tree count and notify the tree:
			this.treeView.rowCount -= count;
			this.treeBox.rowCountChanged(ranges[i].start, -count);
		}

		// Collapse list by removing all the null entries
		for (var i=0; i < this.getFieldRules().length; i++) {
			if (!this.getFieldRules()[i]) {
				var j = i;
				while (j < this.getFieldRules().length && !this.getFieldRules()[j])
					j++;
				this.getFieldRules().splice(i, j-i);
			}
		}
		
		// Clear out selections
		this.selection.select(-1); 
		
		// End of update batch:
		this.treeBox.endUpdateBatch();	
		
		// Update the preferences:
		this.setFieldRules();

		// Re-initialize the simple interface:
		this.initSimpleInterface();
	},

	handleKeyPress: function(event) {
		if(event.keyCode == 46) {
			this.removeSelectedFieldRules();
		} else if(event.ctrlKey && event.which == 97) {
			if(this.tree && this.selection) {
				try {
					// Select all rows:
					this.selection.selectAll();	
				} catch(e) {
					this.log(e);
				}
			}
		}
	},

	getGlobalFieldRules: function() {		
		// Return the fieldRules for the selected global profile if globalProfileIndex is not out of range:
		if(this.getGlobalProfileIndex() >= 0 && this.getGlobalProfileIndex() < this.getProfileLabels().length) {
			return this.getFieldRules(this.getGlobalProfileIndex());
		} else {
			this.globalProfileIndex = 0;
			return this.getFieldRules(0);
		}		
	},

	getFileContent: function(file) {
		var fileContent = null;
		try {
			var fis = Components.classes['@mozilla.org/network/file-input-stream;1']
						.createInstance(Components.interfaces.nsIFileInputStream);
			fis.init(file, -1, 0, 0);
			var is = Components.classes['@mozilla.org/intl/converter-input-stream;1']
						.createInstance(Components.interfaces.nsIConverterInputStream);
			is.init(fis, 'UTF-8', 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
			if(is instanceof Components.interfaces.nsIUnicharLineInputStream) {
				var line = {};
				var cont;
				do {
					cont = is.readLine(line);
					if(fileContent == null) {
						fileContent = line.value;
					} else {
						fileContent += '\n'+line.value;
					}
				} while (cont);
			}
			is.close();
			fis.close();
		} catch(e) {
			this.log(e);
		}
		return fileContent;
	},

	setFileContent: function(file, str) {
		try {
			var fos = Components.classes['@mozilla.org/network/file-output-stream;1']
						.createInstance(Components.interfaces.nsIFileOutputStream);
			fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
			var os = Components.classes['@mozilla.org/intl/converter-output-stream;1']
						.createInstance(Components.interfaces.nsIConverterOutputStream);
			os.init(fos, 'UTF-8', 4096, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
			os.writeString(str);
			os.close();
			fos.close();
		} catch(e) {
			this.log(e);
		}
	},
	
	getFieldRulesFile: function() {
		var file = this.getConfigDirectory();
		file.append('fieldRules.txt');
		if(!file.exists()) {
			file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
		}
		return file;
	},

	getConfigDirectory: function() {
		var configDirectory; 
		if(this.autofillFormsPrefs.prefHasUserValue('configDirectory')) {
			try {
				configDirectory = this.autofillFormsPrefs.getComplexValue(
										'configDirectory',
										Components.interfaces.nsILocalFile
				);
			} catch(e) {
				this.autofillFormsPrefs.clearUserPref('configDirectory');
			}			
		}
		if(!configDirectory) {
			configDirectory = this.getDefaultConfigDirectory();
		}
		return configDirectory;
	},

	getDefaultConfigDirectory: function() {
		// Use a directory "autofillForms@blueimp.net" inside Firefox profile directory as default:
		var configDirectory = Components.classes['@mozilla.org/file/directory_service;1']
								.getService(Components.interfaces.nsIProperties)
								.get('ProfD', Components.interfaces.nsILocalFile);
		configDirectory.append('autofillForms@blueimp.net');
		if(!configDirectory.exists()) {
		   configDirectory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0770);
		}
		return configDirectory;
	},
	
	setConfigDirectory: function(textBox) {
		try {		
			// Create a file picker instance:
			var fp = Components.classes['@mozilla.org/filepicker;1']
						.createInstance(Components.interfaces.nsIFilePicker);
			
			// Initialize the file picker window:
			fp.init(
				window,
				this.getStringBundle().getString('selectConfigDirectory'),
				Components.interfaces.nsIFilePicker.modeGetFolder
			);
			
			// Show the file picker window:
			var rv = fp.show();
			
			if (rv == Components.interfaces.nsIFilePicker.returnOK) {
				var newDir = fp.file;
				if(newDir.path == this.getConfigDirectory().path) {
					return;
				}
				this.moveConfigFiles(newDir);				
				// Save the selected directory in the preferences:
				this.autofillFormsPrefs.setComplexValue(
					'configDirectory',
					Components.interfaces.nsILocalFile, newDir
				);
				if(textBox) {
					// Set the textbox value to the directory path:
					textBox.value = newDir.path;
				}
			}
		} catch(e) {
			this.log(e);
		}
	},
	
	resetConfigDirectory: function(textBox) {
		if(this.autofillFormsPrefs.prefHasUserValue('configDirectory')) {		
			var newDir = this.getDefaultConfigDirectory();
			this.moveConfigFiles(newDir);
			this.autofillFormsPrefs.clearUserPref('configDirectory');
			if(textBox) {
				// Set the textbox value to an empty string:
				textBox.value = '';
			}
		}
	},
	
	openConfigDirectory: function() {
		var configDirectory = this.getConfigDirectory();
		if(configDirectory) {
			try {
				// Open the config directory in the operating system file manager:
				configDirectory.reveal();
			} catch(e) {
				// reveal method may not be supported on some platforms,
				// use nsIExternalProtocolService instead: 
				var uri = Components.classes["@mozilla.org/network/io-service;1"]
					.getService(Components.interfaces.nsIIOService)
					.newFileURI(configDirectory);		
				var protocolSvc = 
				Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
					.getService(Components.interfaces.nsIExternalProtocolService);
				protocolSvc.loadUrl(uri);
			}
		}
	},
	
	moveConfigFiles: function(newDir) {
		if(this.checkConfigDirectoryOverwrite(newDir)) {
			this.moveFile(this.getFieldRulesFile(), newDir);
			this.moveFile(this.getDynamicTagsFile(), newDir);
			this.moveFile(this.getDynamicTagCodesFile(), newDir);
			this.moveFile(this.getProfileLabelsFile(), newDir);
			this.moveFile(this.getProfileSiteRulesFile(), newDir);
			return true;
		}
		return false;
	},
	
	importFromConfigDirectory: function() {
		var ok = true;
		if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
			ok = this.getPrompts().confirm(
				window,
				this.getStringBundle().getString('importFromConfigDirectoryTitle'),
				this.getStringBundle().getString('importFromConfigDirectoryText')
			);
		}
		if(ok) {
			this.importFieldRulesFromConfigDirectory();
			this.importDynamicTagsFromConfigDirectory();
			this.importDynamicTagCodesFromConfigDirectory();
			this.importProfileLabelsFromConfigDirectory();
			this.importProfileSiteRulesFromConfigDirectory();
		}
	},
	
	exportToConfigDirectory: function() {
		if(this.checkConfigDirectoryOverwrite(this.getConfigDirectory())) {
			this.exportFieldRulesToConfigDirectory();
			this.exportDynamicTagsToConfigDirectory();
			this.exportDynamicTagCodesToConfigDirectory();
			this.exportProfileLabelsToConfigDirectory();
			this.exportProfileSiteRulesToConfigDirectory();
			return true;
		}
		return false;
	},
	
	moveFile: function(file, newDir, newFileName) {
		try {
			// The new fileName - uses the current fileName if empty:
			newFileName = (typeof newFileName == 'string') ? newFileName : null;
			
			file.moveTo(newDir, newFileName);
			return true;
		} catch(e) {
			this.log(e);
			return false;
		}
	},
	
	checkConfigDirectoryOverwrite: function(newDir) {
		var ok = true;
		if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
			if(newDir.directoryEntries.hasMoreElements()) {
				ok = this.getPrompts().confirm(
					window,
					this.getStringBundle().getString('newConfigDirectoryNotEmptyTitle'),
					this.getStringBundle().getString('newConfigDirectoryNotEmptyText')
				);
			}
		}
		return ok;
	},

	exportFieldRulesToConfigDirectory: function() {
		var prefString;
		// Get the fieldRules string from the preferences:
		prefString = this.autofillFormsPrefs
							.getComplexValue('fieldRules',Components.interfaces.nsIPrefLocalizedString)
							.data;
		if(prefString) {
			this.setFileContent(this.getFieldRulesFile(), prefString);
		}
	},
	
	importFieldRulesFromConfigDirectory: function() {
		var prefString;
		prefString = this.getFileContent(this.getFieldRulesFile());
		if(prefString) {
			// Store the fieldRules as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'fieldRules',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},

	backupFieldRules: function() {
		// Get the fieldRules string from the preferences:
		return this.autofillFormsPrefs
							.getComplexValue('fieldRules',Components.interfaces.nsISupportsString)
							.data;
	},

	restoreFieldRules: function() {
		if(this.cachedFieldRules) {
			// Store the fieldRules as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'fieldRules',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(this.cachedFieldRules)
			);
		}
	},
	
	getFieldRules: function(profileIndex) {
		if(this.fieldRules == null) {
			this.fieldRules = new Array();

			var prefString;		
			if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
				// Get the fieldRules string from the fieldRules file in the configDirectory:
				prefString = this.getFileContent(this.getFieldRulesFile());
			}		
			if(!prefString) {
				// Get the fieldRules string from the preferences:
				prefString = this.autofillFormsPrefs
									.getComplexValue('fieldRules',Components.interfaces.nsIPrefLocalizedString)
									.data;
			}
			
			// On change of the "storeEncrypted" setting, we must decrypt or may not decrypt
			// the prefString in opposition to the setting - the "invertedSetting" helper var
			// helps to identify this situation:
			var boolTest = this.invertedSetting ? false : true;
			
			// If the fieldRules are stored encrypted, decrypt the prefString:
			if(this.autofillFormsPrefs.getBoolPref('storeEncrypted') == boolTest) {
				try {
					// nsISecretDecoderRing fails to handle characters above ISO-8859-1 charset
					// The usage of encodeURI/decodeURI on the fieldRule properties bypasses this problem
					prefString = this.getCryptoService().decryptString(prefString);
				} catch(e) {
					// Decrypting failed - return an empty default profile:
					this.fieldRules.push(new Array());
					this.profileIndex = 0;
					return this.fieldRules[0];
				}
			}
			
			// Get the profiles (separated by \n\n):
			var profiles = prefString.split('\n\n');
		
			for(var i=0; i<profiles.length; i++) {
				// Create an array for each profile:
				this.fieldRules.push(new Array());
				
				// Get the fieldRules rows (separated by \n):
				var rows = profiles[i].split('\n');
				if(rows[0]) {
					for(var j=0; j<rows.length; j++) {
						if(!rows[j])
							continue;
							
						// Get the fieldRules column items (separated by \t):
						var cols = rows[j].split('\t');
												
						// Create fieldRules objects and save them in the current fieldRules Array:
						if(cols.length && cols.length == 6) {

							// Decode the fieldRule properties:
							for(var k=0; k<cols.length; k++) {
								cols[k] = decodeURI(cols[k]);
							}

							this.fieldRules[i].push(
								this.createFieldRule(
									cols[0],cols[1],cols[2],cols[3],
									(cols[4] != 'false'),
									(cols[5] != 'false')
								)
							);
						}
					}
				} else
					this.fieldRules[i] = new Array();
			}
		}

		profileIndex = (typeof profileIndex != 'undefined') ? profileIndex : this.getProfileIndex();
		
		// Return the fieldRules for the selected profile if profileIndex is not out of range:
		if(profileIndex >= 0 && profileIndex < this.fieldRules.length)
			return this.fieldRules[profileIndex];
		else {
			this.profileIndex = 0;
			if(this.fieldRules[0] == null)
				this.fieldRules[0] = new Array();
			return this.fieldRules[0];
		}
	},
	
	setFieldRules: function() {
		if(this.fieldRules == null) {
			// Initialize the field rules:
			this.getFieldRules();
		}
			
		var profiles = '';
		var rows, cols;
		for(var i=0; i < this.fieldRules.length; i++) {
			rows = '';
			for(var j=0; j<this.fieldRules[i].length; j++) {
				cols = null;
				for(var property in this.fieldRules[i][j]) {
					if(cols == null)
						cols = '';
					else
						cols += '\t';
					// Encode the fieldRule property before adding it to the string:
					cols += encodeURI(this.fieldRules[i][j][property]);
				}
				if(j!=0)
					rows += '\n';
				rows += cols;
			}
			if(i!=0)
				profiles += '\n\n';
			profiles += rows;
		}

		// If the fieldRules are to be stored encrypted, encrypt the prefString:
		if(this.autofillFormsPrefs.getBoolPref('storeEncrypted')) {
			try {
				// nsISecretDecoderRing fails to handle characters above ISO-8859-1 charset
				// The usage of encodeURI/decodeURI on the fieldRule properties bypasses this problem
				profiles = this.getCryptoService().encryptString(profiles);
			} catch(e) {
				// Decrypting failed - return:
				return;
			}
		}

		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			this.setFileContent(this.getFieldRulesFile(), profiles);
		} else {
			// Store the fieldRules objects as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'fieldRules',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(profiles)
			);
		}
	},
	
	copyFieldRules: function(origin) {
		var copy = new Array();
		for(var i=0; i<origin.length; i++) {
			copy.push(
				this.createFieldRule(
					origin[i]['fieldRuleName'],
					origin[i]['fieldRuleValue'],
					origin[i]['fieldRuleFieldRule'],
					origin[i]['fieldRuleSiteRule'],
					origin[i]['fieldRuleOverwrite'],
					origin[i]['fieldRuleEnabled']
				)
			)
		}
		return copy;
	},
	importDynamicTagsFromSettings: function() {
		//Here the tags are added directly
		var prefString = null;

		prefString = this.autofillFormsPrefs
							.getComplexValue('dynamicTags',Components.interfaces.nsIPrefLocalizedString)
							.data;
		var dynamicTags = prefString.split('\t');

		prefString = this.autofillFormsPrefs
							.getComplexValue('dynamicTagCodes',Components.interfaces.nsIPrefLocalizedString)
							.data;
		var dynamicTagCodes = prefString.split('\t');

		this.importDynamicTags(dynamicTags, dynamicTagCodes);
		this.setDynamicTags(dynamicTags);
		this.setDynamicTagCodes(dynamicTagCodes);
	},
	importDynamicTagsFromTagEditor: function() {
		//Here the tags are added to the editor window, can be cancelled if necessary
		var richlistbox = document.getElementById('tagList');
		if(richlistbox) {
			var richlistitems = richlistbox.getElementsByTagName('richlistitem');
			var textboxes;
			
			var dynamicTags = new Array();
			var dynamicTagCodes = new Array();
			
			// Go through the richlistbox items:
			for(var i=0; i<richlistitems.length; i++) {
				textboxes = richlistitems[i].getElementsByTagName('textbox');
				
				// Add the dynamic tags and their associated tag codes to the lists:
				if (textboxes[0].value != '' && textboxes[1].value != '') {
					dynamicTags.push(this.makeSafe(textboxes[0].value));
					dynamicTagCodes.push(this.makeSafe(textboxes[1].value));
				}
			}
			this.importDynamicTags(dynamicTags, dynamicTagCodes);
		}
	},
	importDynamicTags: function(dynamicTags, dynamicTagCodes) {
		try {
			var file = this.filePicker('modeOpen', this.getStringBundle().getString('importDynamicTags'));
			if(file) {
				var fis = Components.classes['@mozilla.org/network/file-input-stream;1']
								.createInstance(Components.interfaces.nsIFileInputStream);
				fis.init(file, -1, 0, 0);

				var is = Components.classes['@mozilla.org/intl/converter-input-stream;1']
							.createInstance(Components.interfaces.nsIConverterInputStream);
				is.init(fis, 'UTF-8', 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

				if (is instanceof Components.interfaces.nsIUnicharLineInputStream) {					
					var line = {};
					var cont;
					var firstLine = true;
					var i;

					do {
						cont = is.readLine(line);

						// Get the dynamicTags column items (separated by \t) from the file:
						var cols = line.value.split('\t');
						
						if (firstLine == true) {
							// The first row has the following syntax (added in version 0.9.5):
							// autofillForms@blueimp.net	Version	dynamictags
							if(cols.length && cols.length != 3)
								cont = false;
							else if (cols[0] != 'autofillForms@blueimp.net')
								cont = false;
							else if (cols[2] != 'dynamictags')
								cont = false;
							firstLine = false;
						}
						else if (cols.length && cols.length == 2) {
							//Check the imported pair isn't already defined or empty.
							i = dynamicTags.indexOf(cols[0]);
							if ((i >= 0 && dynamicTagCodes[i] == cols[1])||(cols[0]=='' && cols[1]=='')) {
								continue;
							} else {
								dynamicTags.push(cols[0]);
								dynamicTagCodes.push(cols[1]);
								this.tagEditorAdd(cols[0],cols[1])
							}
						}
					} while (cont);

				}
				is.close();
				fis.close();
			}
		} catch(e) {
			this.log(e);
		}
	},
	exportDynamicTagsFromSettings: function() {
		var prefString = null;

		// Write the tags to file analog to storing them in the preferences:
		prefString = this.autofillFormsPrefs
							.getComplexValue('dynamicTagCodes',Components.interfaces.nsIPrefLocalizedString)
							.data;
		var dynamicTagCodes = prefString.split('\t');

		prefString = this.autofillFormsPrefs
							.getComplexValue('dynamicTags',Components.interfaces.nsIPrefLocalizedString)
							.data;
		var dynamicTags = prefString.split('\t');
		this.exportDynamicTags(dynamicTags, dynamicTagCodes);
	},
	exportDynamicTagsFromTagEditor: function() {
		var richlistbox = document.getElementById('tagList');
		if(richlistbox) {
			var richlistitems = richlistbox.getElementsByTagName('richlistitem');
			var textboxes;
			
			var dynamicTags = new Array();
			var dynamicTagCodes = new Array();
			
			// Go through the richlistbox items:
			for(var i=0; i<richlistitems.length; i++) {
				textboxes = richlistitems[i].getElementsByTagName('textbox');
				
				// Add the dynamic tags and their associated tag codes to the lists:
				if (textboxes[0].value != '' && textboxes[1].value != '') {
					dynamicTags.push(this.makeSafe(textboxes[0].value));
					dynamicTagCodes.push(this.makeSafe(textboxes[1].value));
				}
			}
			this.exportDynamicTags(dynamicTags, dynamicTagCodes);
		}
	},
	exportDynamicTags: function(dynamicTags, dynamicTagCodes) {
		try {
			var file = this.filePicker(
				'modeSave',
				this.getStringBundle().getString('exportDynamicTags'),
				this.getProfileLabel(this.getProfileIndex())+'.txt'
			);
			if(file) {
				var fos = Components.classes['@mozilla.org/network/file-output-stream;1'].
										createInstance(Components.interfaces.nsIFileOutputStream);
				fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
				
				var os = Components.classes['@mozilla.org/intl/converter-output-stream;1']
							.createInstance(Components.interfaces.nsIConverterOutputStream);
				os.init(fos, 'UTF-8', 4096, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
				
				var em = Components.classes['@mozilla.org/extensions/manager;1']
							.getService(Components.interfaces.nsIExtensionManager);
							
				var header = 'autofillForms@blueimp.net' + '\t'
								+ em.getItemForID('autofillForms@blueimp.net').version + '\t'
								+ 'dynamictags\n';
				os.writeString(header);
				
				var cols;
				for(var i=0; i<dynamicTags.length; i++) {
					cols = dynamicTags[i]+'\t'+dynamicTagCodes[i];
					os.writeString('\n' + cols);
				}
				os.close();
				fos.close();				
			}
		} catch(e) {
			this.log(e);
		}
	},
	importProfile: function() {
		try {
			var file = this.filePicker('modeOpen', this.getStringBundle().getString('importProfile'));
			if(file) {
				var fis = Components.classes['@mozilla.org/network/file-input-stream;1']
								.createInstance(Components.interfaces.nsIFileInputStream);
				fis.init(file, -1, 0, 0);

				var is = Components.classes['@mozilla.org/intl/converter-input-stream;1']
							.createInstance(Components.interfaces.nsIConverterInputStream);
				is.init(fis, 'UTF-8', 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);

				if(this.fieldRules == null) {
					// Initialize the field rules:
					this.getFieldRules();
				}
				
				// Create a new fieldRule profile (index is incremented when a valid header is found):
				var newProfileLabel = '';
				var newProfileSiteRule = '(?:)';
				var newProfileIndex = this.fieldRules.length-1;
				var validProfile = false;

				if (is instanceof Components.interfaces.nsIUnicharLineInputStream) {					
					var line = {};
					var cont;
					do {
						cont = is.readLine(line);

						// Get the fieldRules column items (separated by \t) from the file:
						var cols = line.value.split('\t');
						
						if(cols.length && cols.length < 6 && cols[0] == 'autofillForms@blueimp.net') {
							// The first row has the following syntax (added SiteRule for version 0.9.1):
							// autofillForms@blueimp.net	Version	Label	SiteRule
							// Every time such a row is encountered, a new profile is generated.
							if(cols.length >= 3) {
								newProfileLabel = cols[2];	
								// Add profile label for default empty profile:
								if(this.getProfileLabels().length == 0) {
									this.getProfileLabels().push(this.getUniqueProfileLabel());
								}
								// Add the newProfileLabel to the profileLabels list (make sure it is unique):
								this.getProfileLabels().push(this.getUniqueProfileLabel(newProfileLabel));
							}
							if(cols.length >= 4) {
								try {
									newProfileSiteRule = this.getRegExpStr(cols[3]);
									// Add a new profileSiteRule:
									this.getProfileSiteRules().push(newProfileSiteRule);
								} catch(e) {
									// Catch missing or invalid site rule
								}
							}
							// Increment the ProfileIndex
							newProfileIndex += 1;
							this.fieldRules.push(new Array());
							validProfile = true;
						} else if(cols.length && cols.length == 6 && validProfile == true) {
							// Create fieldRules objects and save them in the fieldRules Array:
							this.fieldRules[newProfileIndex].push(
								this.createFieldRule(
									cols[0],cols[1],cols[2],cols[3],
									(cols[4] != 'false'),
									(cols[5] != 'false')
								)
							);
						}							

					} while (cont);
				}

				// Save the profileLabels list in the preferences:
				this.setProfileLabels(this.getProfileLabels());
				// Save the profileSiteRules in the preferences:
				this.setProfileSiteRules(this.getProfileSiteRules());				
				// Update the profiles lists:
				this.initProfilesLists();

				// Update the fieldRules:
				this.setFieldRules();

				is.close();
				fis.close();
			}
		} catch(e) {
			this.log(e);
		}
	},
	exportProfile: function() {
		try {
			var file = this.filePicker(
				'modeSave',
				this.getStringBundle().getString('exportProfile'),
				this.getProfileLabel(this.getProfileIndex())+'.txt'
			);
			if(file) {
				var fos = Components.classes['@mozilla.org/network/file-output-stream;1'].
										createInstance(Components.interfaces.nsIFileOutputStream);
				fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
				
				var os = Components.classes['@mozilla.org/intl/converter-output-stream;1']
							.createInstance(Components.interfaces.nsIConverterOutputStream);
				os.init(fos, 'UTF-8', 4096, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
				
				var profileIndex = this.getProfileIndex();
				this.writeProfile(os, profileIndex);

				os.close();
				fos.close();				
			}
		} catch(e) {
			this.log(e);
		}
	},
	exportAllProfiles: function() {
		try {
			//use the first profile label as the filename
			var file = this.filePicker(
				'modeSave',
				this.getStringBundle().getString('exportProfile'),
				this.getProfileLabel(0)+'.txt'
			);
			if(file) {
				var fos = Components.classes['@mozilla.org/network/file-output-stream;1'].
										createInstance(Components.interfaces.nsIFileOutputStream);
				fos.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
				
				var os = Components.classes['@mozilla.org/intl/converter-output-stream;1']
							.createInstance(Components.interfaces.nsIConverterOutputStream);
				os.init(fos, 'UTF-8', 4096, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
				
				for(var i=0; i<this.getProfileLabels().length; i++) {
					this.writeProfile(os, i);
					os.writeString('\n\n');
				}
				os.close();
				fos.close();				
			}
		} catch(e) {
			this.log(e);
		}
	},
	writeProfile: function(os, profileIndex) {
		try {
			var em = Components.classes['@mozilla.org/extensions/manager;1']
						.getService(Components.interfaces.nsIExtensionManager);
							
			var header = 'autofillForms@blueimp.net' + '\t'
							+ em.getItemForID('autofillForms@blueimp.net').version + '\t'
							+ this.getProfileLabel(profileIndex) + '\t'
							+ this.getProfileSiteRule(profileIndex) + '\n';
			os.writeString(header);
			
			// Write the rules to file analog to storing them in the preferences:
			var cols;
			for(var i=0; i<this.getFieldRules(profileIndex).length; i++) {
				cols = null;
				for(var property in this.getFieldRules(profileIndex)[i]) {
					if(cols == null)
						cols = '';
					else
						cols += '\t';
					cols += this.getFieldRules(profileIndex)[i][property];
				}
				os.writeString('\n' + cols);
			}
		} catch(e) {
			this.log(e);
		}
	},
	filePicker: function(mode, title, fileName) {
		try {		
			// Create a file picker instance:
			var fp = Components.classes['@mozilla.org/filepicker;1']
						.createInstance(Components.interfaces.nsIFilePicker);

			// The filename suggested to the user as a default:
			if(fileName) {
				fp.defaultString = fileName;
			}
						
			// Initialize the file picker window:
			fp.init(
				window,
				title,
				Components.interfaces.nsIFilePicker[mode]
			);
			
			// Show the file picker window:
			var rv = fp.show();
			
			if(rv==Components.interfaces.nsIFilePicker.returnOK || rv==Components.interfaces.nsIFilePicker.returnReplace)
				return fp.file;
			else
				return null;
		} catch(e) {
			return null;
		}
	},

	getUnicodeString: function(stringData) {
		// Create an Unicode String:
		var str = Components.classes['@mozilla.org/supports-string;1']
					.createInstance(Components.interfaces.nsISupportsString);
		// Set the String value:
		str.data = stringData;
		// Return the Unicode String:
		return str;
	},

	getStringBundle: function() {
		return document.getElementById('autofillFormsStringBundle');
	},

	getDoc: function(win) {
		if(win)
			return win.document;
		else if(content)
			return content.document;
		else
			return this.getBrowser().contentDocument;
	},

	getWin: function() {
		if(content)
			return content;
		else
			return this.getBrowser().contentWindow;
	},
	
	getBrowser: function() {
		try {
			return gBrowser;
		} catch(e) {
			// gBrowser is not available, so make use of the WindowMediator service instead:
			return this.getWindowMediator().getMostRecentWindow('navigator:browser').getBrowser();
		}
	},

	getWindowMediator: function() {
		return Components.classes['@mozilla.org/appshell/window-mediator;1']
				.getService(Components.interfaces.nsIWindowMediator);
	},

	getRegExpStr: function(str) {
		// Create a RegExp object using the given String:
		var regExpStr = new RegExp(str).toString();
		// Return the String representation without the surrounding slashes:
		return regExpStr.substr(1,regExpStr.length-2);
	},

	getPrefManager: function() {
		return Components.classes['@mozilla.org/preferences-service;1']
				.getService(Components.interfaces.nsIPrefService);
	},
	
	getCryptoService: function() {
		return Components.classes['@mozilla.org/security/sdr;1']
				.createInstance(Components.interfaces.nsISecretDecoderRing);
	},
	
	getPrompts: function() {
		return Components.classes['@mozilla.org/embedcomp/prompt-service;1']
				.getService(Components.interfaces.nsIPromptService);
	},

	recognizeMouseButton: function(event) {
		var modifiers = new Array();
		
		// Get the modifiers:
		if(event.altKey) modifiers.push('alt');
		if(event.ctrlKey) modifiers.push('control');
		if(event.metaKey) modifiers.push('meta');
		if(event.shiftKey) modifiers.push('shift');
		
		// Return a mouseButtonObj:
		return this.mouseButtonFactory(modifiers, 'mousebutton'+event.button);
	},
	
	mouseButtonFactory: function(modifiers, mouseButton) {
		if(typeof arguments.callee.mouseButtonObj == 'undefined') {
			arguments.callee.mouseButtonObj = function(modifiers, mouseButton) {
				this.modifiers = modifiers ? modifiers : new Array();
				this.mouseButton = mouseButton;
				this.toString = function() {
					if(this.modifiers.length) {
						return this.modifiers.join('+')+'+'+this.mouseButton;
					} else {
						return this.mouseButton;
					}
				}
				this.equals = function(mouseButtonObj) {
					if(this.mouseButton != mouseButtonObj.mouseButton) {
						return false;
					}
					if(this.modifiers.length != mouseButtonObj.modifiers.length) {
						return false;
					}
					for(var i=0; i<this.modifiers.length; i++) {
						if(this.modifiers[i] != mouseButtonObj.modifiers[i]) {
							return false;
						}
					}
					return true;
				}
				return this;
			}
		}
		return new arguments.callee.mouseButtonObj(modifiers, mouseButton);
	},

	getFormattedMouseButton: function(mouseButtonObj) {
		var formattedMouseButton = '';
		if(!mouseButtonObj.mouseButton) {
			return formattedMouseButton;
		}
		// Add the modifiers:
		for(var i=0; i < mouseButtonObj.modifiers.length; i++) {
			try {
				formattedMouseButton += this.getStringBundle().getString(mouseButtonObj.modifiers[i])+'+';
			} catch(e) {
				this.log(e);
				// Error in shortcut string, return empty String:
				return '';
			}
		}
		try {
			formattedMouseButton += this.getStringBundle().getString(mouseButtonObj.mouseButton);
		} catch(e) {
			// No localization for this mouse button, add generic button string :
			formattedMouseButton += this.getStringBundle().getString('mousebutton');
			// Add the index of the given mouseButton:
			formattedMouseButton += ' '+mouseButtonObj.mouseButton.substr('mousebutton'.length);
		}
		return formattedMouseButton;
	},
	
	applyMouseButton: function(event, id) {
		// Recognize the mouse button event:
		var mouseButtonObj = this.recognizeMouseButton(event);
		if(!mouseButtonObj)
			return;
		// Ignore the right mouse button (mousebutton2), as this already invokes the context menu:
		if(mouseButtonObj.mouseButton == 'mousebutton2') {
			return;
		}
		// Save the new mouse button object:
		this.setMouseButton(id, mouseButtonObj);
		// Update the mouse button textbox:
		if(event.view.document && event.view.document.getElementById(id)) {
			event.view.document.getElementById(id).value = this.getFormattedMouseButton(mouseButtonObj);
		}
	},
	
	disableMouseButton: function(event, id) {
		// Disable the mouse button:
		this.setMouseButton(id, null);
		// Update the mouse button textbox:
		if(event.view.document && event.view.document.getElementById(id)) {
			event.view.document.getElementById(id).value = '';
		}
	},

	getMouseButton: function(id) {
		if(this.mouseButton == null) {
			// Create a new mouseButton container object:
			this.mouseButton = new Object();
		}
		if(this.mouseButton[id] == null) {
			var mouseButtonItems = this.autofillFormsPrefs.getCharPref(id).split('+');
			var mouseButton;
			if(mouseButtonItems.length == 0) {
				mouseButton = '';
			} else {
				// Remove the last element and save it as mouseButton
				// the remaining mouseButtonItems are the modifiers:
				mouseButton = mouseButtonItems.pop();
			}			
			// Create a new mouseButton object:
			this.mouseButton[id] = this.mouseButtonFactory(mouseButtonItems, mouseButton);
		}
		return this.mouseButton[id];
	},
	
	setMouseButton: function(id, mouseButtonObj) {
		var stringData;
		if(mouseButtonObj) {
			stringData = mouseButtonObj.toString();
		} else {
			stringData = '';
		}
		// Save the mouseButtonObj as Unicode String in the preferences:
		this.autofillFormsPrefs.setComplexValue(
			id,
			Components.interfaces.nsISupportsString,
			this.getUnicodeString(stringData)
		);
	},
	
	recognizeKeys: function(event) {
		var modifiers = new Array();
		var key = '';
		var keycode = '';
		
		// Get the modifiers:
		if(event.altKey) modifiers.push('alt');
		if(event.ctrlKey) modifiers.push('control');
		if(event.metaKey) modifiers.push('meta');
		if(event.shiftKey) modifiers.push('shift');
		
		// Get the key or keycode:
		if(event.charCode) {
			key = String.fromCharCode(event.charCode).toUpperCase();
		} else {
			// Get the keycode from the keycodes list:
			keycode = this.getKeyCodes()[event.keyCode];
			if(!keycode) {
				return null;
			}
		}
		
		// Shortcut may be anything, but not 'VK_TAB' alone (without modifiers),
		// as this button is used to change focus to the 'Apply' button: 
		if(modifiers.length > 0 || keycode != 'VK_TAB') {
			return this.shortcutFactory(modifiers, key, keycode);
		}
		return null;
	},

	shortcutFactory: function(modifiers, key, keycode) {
		if(typeof arguments.callee.shortcut == 'undefined') {
			arguments.callee.shortcut = function(modifiers, key, keycode) {
				this.modifiers = modifiers ? modifiers : new Array();
				this.key = key;
				this.keycode = keycode;
				this.toString = function() {
					if(this.modifiers.length) {
						return this.modifiers.join('+')+'+'+this.key+this.keycode;
					} else {
						return this.key+this.keycode;
					}
				}
				this.equals = function(shortcut) {
					if(this.key != shortcut.key) {
						return false;
					}
					if(this.keycode != shortcut.keycode) {
						return false;
					}
					if(this.modifiers.length != shortcut.modifiers.length) {
						return false;
					}
					for(var i=0; i<this.modifiers.length; i++) {
						if(this.modifiers[i] != shortcut.modifiers[i]) {
							return false;
						}
					}
					return true;
				}
				return this;
			}
		}
		return new arguments.callee.shortcut(modifiers, key, keycode);
	},
	
	getKeyCodes: function() {
		var keycodes = new Array();
		// Get the list of keycodes from the KeyEvent object:
		for(var property in KeyEvent) {
			keycodes[KeyEvent[property]] = property.replace('DOM_','');
		}
		// VK_BACK_SPACE (index 8) must be VK_BACK:
		keycodes[8] = 'VK_BACK';
		return keycodes;
	},

	applyShortcut: function(event, id) {
		// Recognize the pressed keys:
		var shortcut = this.recognizeKeys(event)
		if(!shortcut)
			return;
		// Save the new shortcut:
		this.setShortcut(id, shortcut);
		// Update the shortcut textbox:
		if(event.view.document && event.view.document.getElementById(id)) {
			event.view.document.getElementById(id).value = this.getFormattedShortcut(shortcut);
		}
	},
	
	disableShortcut: function(event, id) {
		// Disable the shortcut:
		this.setShortcut(id, null);
		// Update the shortcut textbox:
		if(event.view.document && event.view.document.getElementById(id)) {
			event.view.document.getElementById(id).value = '';
		}
	},
	
	getShortcut: function(id) {
		if(this.shortcut == null) {
			// Create a new shortcut container object:
			this.shortcut = new Object();
		}
		if(this.shortcut[id] == null) {
			var key = null;
			var keycode = null;
			var shortcutItems = this.autofillFormsPrefs
								.getComplexValue(id,Components.interfaces.nsIPrefLocalizedString)
								.data.split('+');
			if(shortcutItems.length > 0) {
				// Remove the last element and save it as key
				// the remaining shortcutItems are the modifiers:
				key = shortcutItems.pop();
				// Check if the key is a keycode:
				if(key.indexOf('VK') == 0) {
					keycode	= key;
					key = null;
				}
			}			
			// Create a new shortcut object:
			this.shortcut[id] = this.shortcutFactory(shortcutItems, key, keycode);
		}
		return this.shortcut[id];
	},
	
	setShortcut: function(id, shortcut) {
		var stringData;
		if(shortcut) {
			stringData = shortcut.toString();
		} else {
			stringData = '';
		}
		// Save the shortcut as Unicode String in the preferences:
		this.autofillFormsPrefs.setComplexValue(
			id,
			Components.interfaces.nsISupportsString,
			this.getUnicodeString(stringData)
		);
	},
	
	updateShortcut: function(id) {
		if(this.shortcut == null) {
			this.shortcut = new Object();
		}
		// Setting the shortcut object to "null" will update it on the next getShortcut() call:
		this.shortcut[id] = null;
		
		// Get the keyboard shortcut elements:
		var modifiers = this.getShortcut(id).modifiers.join(' ');
		var key = this.getShortcut(id).key;
		var keycode = this.getShortcut(id).keycode;
		
		var domId = 'autofillForms' + id.replace(/shortcut/, 'Shortcut');
		var command = 'autofillForms' + id.replace(/shortcut/i, '');

		// Remove current key if existing:
		if(document.getElementById(domId)) {
			document.getElementById('mainKeyset').removeChild(
				document.getElementById(domId)
			);
		}
		
		// Check if keyboard shortcut is enabled:
		if(key || keycode) {
			// Create a key element:
			var keyNode = document.createElement('key');
			
			keyNode.setAttribute('id', domId);
			keyNode.setAttribute('command', command);
			
			// Set the key attributes from saved shortcut:
			keyNode.setAttribute('modifiers', modifiers);
			if(key) {
				keyNode.setAttribute('key', key);
			} else {
				keyNode.setAttribute('keycode', keycode);
			}
			
			// Add the key to the mainKeyset:
			document.getElementById('mainKeyset').appendChild(keyNode);
		}
	},
	
	getFormattedShortcut: function(shortcut) {
		var formattedShortcut = '';
		// Add the modifiers:
		for(var i=0; i < shortcut.modifiers.length; i++) {
			try {
				formattedShortcut += this.getStringBundle().getString(shortcut.modifiers[i]) + '+';
			} catch(e) {
				// Error in shortcut string, return empty String;
				return '';
			}
		}
		if(shortcut.key) {
			// Add the key:
			if(shortcut.key == ' ') {
				formattedShortcut += this.getStringBundle().getString('VK_SPACE');
			} else {
				formattedShortcut += shortcut.key;
			}
		} else if(shortcut.keycode) {
			// Add the keycode (instead of the key):
			try {
				formattedShortcut += this.getStringBundle().getString(shortcut.keycode);
			} catch(e) {
				formattedShortcut += shortcut.keycode.replace('VK_', '');
			}
		}
		return formattedShortcut;
	},

	replaceDynamicTags: function(fieldRuleValue) {
		// Replace all dynamic tags with the return values of their associated tag codes:
		for(var j=0; j<this.getDynamicTags().length; j++) {
			// Catch if the number of tags doesn't match the number of tag codes or if the tag code is invalid:
			try {
				var regExpObj = new RegExp(this.getDynamicTags()[j],'g');
				// We use eval() here without restrictions - the given tagCode must be trusted:
				fieldRuleValue = fieldRuleValue.replace(regExpObj, eval(this.getDynamicTagCodes()[j]));
			} catch(e) {
				this.log(e);
			}
		}
		return fieldRuleValue;
	},

	getDynamicTagsFile: function() {
		var file = this.getConfigDirectory();
		file.append('dynamicTags.txt');
		if(!file.exists()) {
			file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
		}
		return file;
	},

	exportDynamicTagsToConfigDirectory: function() {
		var prefString;
		// Get the dynamicTags string from the preferences:
		prefString = this.autofillFormsPrefs
							.getComplexValue('dynamicTags',Components.interfaces.nsIPrefLocalizedString)
							.data;
		if(prefString) {
			this.setFileContent(this.getDynamicTagsFile(), prefString);
		}
	},
	
	importDynamicTagsFromConfigDirectory: function() {
		var prefString;
		prefString = this.getFileContent(this.getDynamicTagsFile());
		if(prefString) {
			// Store the dynamicTags as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'dynamicTags',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},
	
	getDynamicTags: function() {
		if(this.dynamicTags == null) {
			var prefString;
			if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
				// Get the dynamicTags string from the dynamicTags file in the configDirectory:
				prefString = this.getFileContent(this.getDynamicTagsFile());
			}
			if(!prefString) {
				prefString = this.autofillFormsPrefs
									.getComplexValue('dynamicTags',Components.interfaces.nsIPrefLocalizedString)
									.data;
			}		
			this.dynamicTags = prefString.split('\t');
		}
		return this.dynamicTags;
	},

	setDynamicTags: function(dynamicTags) {
		// Save the dynamic tags separated by tabs:
		var prefString = dynamicTags.join('\t');
		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			this.setFileContent(this.getDynamicTagsFile(), prefString);
		} else {
			this.autofillFormsPrefs.setComplexValue(
				'dynamicTags',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},

	getDynamicTagCodesFile: function() {
		var file = this.getConfigDirectory();
		file.append('dynamicTagCodes.txt');
		if(!file.exists()) {
			file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0660);
		}
		return file;
	},

	exportDynamicTagCodesToConfigDirectory: function() {
		var prefString;
		// Get the dynamicTagCodes string from the preferences:
		prefString = this.autofillFormsPrefs
							.getComplexValue('dynamicTagCodes',Components.interfaces.nsIPrefLocalizedString)
							.data;
		if(prefString) {
			this.setFileContent(this.getDynamicTagCodesFile(), prefString);
		}
	},
	
	importDynamicTagCodesFromConfigDirectory: function() {
		var prefString;
		prefString = this.getFileContent(this.getDynamicTagCodesFile());
		if(prefString) {
			// Store the dynamicTagCodes as unicode string in the preferences:
			this.autofillFormsPrefs.setComplexValue(
				'dynamicTagCodes',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},
	
	getDynamicTagCodes: function() {
		if(this.dynamicTagCodes == null) {
			var prefString;
			if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
				// Get the dynamicTagCodes string from the dynamicTagCodes file in the configDirectory:
				prefString = this.getFileContent(this.getDynamicTagCodesFile());
			}
			if(!prefString) {
				prefString = this.autofillFormsPrefs
									.getComplexValue('dynamicTagCodes',Components.interfaces.nsIPrefLocalizedString)
									.data;
			}
			this.dynamicTagCodes = prefString.split('\t');
		}		
		return this.dynamicTagCodes;
	},

	setDynamicTagCodes: function(dynamicTagCodes) {
		// Save the dynamic tag codes separated by tabs:
		var prefString = dynamicTagCodes.join('\t');
		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			this.setFileContent(this.getDynamicTagCodesFile(), prefString);
		} else {
			this.autofillFormsPrefs.setComplexValue(
			'dynamicTagCodes',
				Components.interfaces.nsISupportsString,
				this.getUnicodeString(prefString)
			);
		}
	},
		
	optionsInitialize: function() {
		this.myLog("Initializing the options window");

		// Save the reference to the Autofill Forms preferences branch:
		this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');

		//Check whether a bounding box can be drawn using the body.getBoundingClientRect method.
		//If not, uncheck and disable the preference item so that it cannot be clicked.
		//Also, disable the CSS modifier textbox
		var doc = this.getDoc(this.getWin());
		if (doc && !doc.body.getBoundingClientRect) {
			document.getElementById('autofillFormsHighlight').setAttribute('disabled', 'true');
			document.getElementById('autofillFormsHighlight').setAttribute('checked', 'false');
			document.getElementById('formHighlightStyleLabel').setAttribute('disabled', 'true');
			document.getElementById('formHighlightStyleBox').setAttribute('disabled', 'true');
		}
		
		// Backing-up the profiles:
		this.backupProfiles();
		// Initialize the profile lists:
		this.initProfilesLists();
		// Initialize the fieldRules tree:
		this.initTree();
		// Initialize the simple interface:
		this.initSimpleInterface();

		// Sort is to be ascending if clicked first:
		this.ascending = true;
		this.profilesAscending = true;

		var configDirectoryTextbox = document.getElementById('configDirectoryTextBox');
		if(configDirectoryTextbox && this.autofillFormsPrefs.prefHasUserValue('configDirectory')) {
			var configDirectory = this.getConfigDirectory();
			if(configDirectory) {
				configDirectoryTextbox.value = configDirectory.path;
			}
		}
		
		// Initialize the keyboard shortcut objects:
		this.shortcut = new Object();
		this.shortcut['shortcut'] = null;
		this.shortcut['shortcutSubmit'] = null;
		this.shortcut['shortcutAllTabs'] = null;
		this.shortcut['shortcutFromProfileSelection'] = null;
		this.shortcut['shortcutProfile'] = null;
		this.shortcut['shortcutSettings'] = null;
		this.shortcut['shortcutDisplayFormDetails'] = null;

		// Display the shortcut combinations:
		for(var property in this.shortcut) {
			if(document.getElementById(property)) {
				document.getElementById(property).value = this.getFormattedShortcut(this.getShortcut(property));
			}
		}
		
		// Initialize the mouse button objects:
		this.mouseButton = new Object();
		this.mouseButton['mouseShortcut'] = null;
		this.mouseButton['mouseShortcutSubmit'] = null;
		this.mouseButton['mouseShortcutAllTabs'] = null;
		this.mouseButton['mouseShortcutFromProfileSelection'] = null;
		this.mouseButton['mouseShortcutProfile'] = null;
		this.mouseButton['mouseShortcutSettings'] = null;
		this.mouseButton['mouseShortcutDisplayFormDetails'] = null;

		// Display the mouse button combinations:
		for(var property in this.mouseButton) {
			if(document.getElementById(property)) {
				document.getElementById(property).value = this.getFormattedMouseButton(this.getMouseButton(property));
			}
		}
		
		// Parse the window params (e.g. initializing the target form field values):
		this.parseOptionsWindowParams();

		// Initialize the help URL drop-down menu:
		this.initHelpUrlMenuList();
	},

	initSimpleInterface: function() {
		var rows = document.getElementById('simpleInterfaceRows');
		if(rows) {
			while(rows.hasChildNodes()) {
				rows.removeChild(rows.firstChild);
			}
			for(var i=0; i<this.getFieldRules().length; i++) {
				// Only show enabled fieldRules:
				if(!this.getFieldRules()[i]['fieldRuleEnabled']) {
					continue;
				}
				rows.appendChild(
					this.getSimpleInterfaceRow(
						i,
						this.getFieldRules()[i]['fieldRuleName'],
						this.getFieldRules()[i]['fieldRuleValue']
					)
				);
			}	
		}
	},

	addSimpleInterfaceRow: function(index) {
		var row = this.getSimpleInterfaceRow(
						index,
						this.getFieldRules()[index]['fieldRuleName'],
						this.getFieldRules()[index]['fieldRuleValue']
					)
		var rows = document.getElementById('simpleInterfaceRows');
		if(rows) {
			var nextSibling;
			if(rows.childNodes) {
				for(var i=index+1; i<rows.childNodes.length; i++) {
					nextSibling = document.getElementById('simpleInterfaceRow_'+i);
					if(nextSibling) {
						rows.insertBefore(row, nextSibling);
						break;
					}
				}
			}
			if(!nextSibling) {
				rows.appendChild(row);
			}
		}
	},
	
	removeSimpleInterfaceRow: function(index) {
		var row = document.getElementById('simpleInterfaceRow_'+index);
		if(row) {
			row.parentNode.removeChild(row);
		}
	},
	
	getSimpleInterfaceRow: function(index, name, value) {
		if(!arguments.callee.row) {
			arguments.callee.row = document.createElement('row');
			arguments.callee.row.setAttribute('align', 'center');
			var label = document.createElement('label');
			var textbox = document.createElement('textbox');
			textbox.setAttribute(
				'onchange',
				'autofillForms.applySimpleInterfaceValue(this)'
			);
			textbox.setAttribute(
				'oninput',
				'this.value=autofillForms.replaceControlCharacters(this.value)'
			);
			textbox.setAttribute('newlines', 'pasteintact');
			arguments.callee.row.appendChild(label);
			arguments.callee.row.appendChild(textbox);	
		}
		var row = arguments.callee.row.cloneNode(true);
		row.setAttribute('id', 'simpleInterfaceRow_'+index);
		row.firstChild.setAttribute('value', name+':');
		row.firstChild.setAttribute('id', 'simpleInterfaceLabel_'+index);
		row.firstChild.setAttribute('control', 'simpleInterfaceTextbox_'+index);
		row.lastChild.setAttribute('id', 'simpleInterfaceTextbox_'+index);
		row.lastChild.setAttribute('value', value);
		// Is textbox a password field?
		if(this.getRegExpPasswordLabel().test(name)) {
			row.lastChild.setAttribute('type', 'password');
		}
		return row;
	},
	
	applySimpleInterfaceValue: function(textBox) {
		// See method selectedFieldRule() why this has to be set to null:
		this.lastSelectedIndex = null;
		
		var index = parseInt(textBox.getAttribute('id').split('_')[1]);
		this.getFieldRules()[index]['fieldRuleValue'] = this.makeSafe(textBox.value);

		// Notify the tree (of the advanced interface):
		try {
			this.treeBox.invalidateRow(index);
		} catch(e) {
		}
			
		// Update the preferences:
		this.setFieldRules();	
	},
	
	updateSimpleInterfaceRow: function(index) {
		var row = document.getElementById('simpleInterfaceRow_'+index);
		if(row) {
			row.firstChild.value = this.getFieldRules()[index]['fieldRuleName']+':';
			row.lastChild.value = this.getFieldRules()[index]['fieldRuleValue'];
			// Is textbox a password field?
			if(this.getRegExpPasswordLabel().test(this.getFieldRules()[index]['fieldRuleName'])) {
				row.lastChild.setAttribute('type', 'password');
			} else {
				row.lastChild.removeAttribute('type');
			}
		}
	},

	getFieldRuleNameForElement: function(element) {
		// Use the form field label as name if available:
		var labelValue = this.getLabelForElement(element);
		// Remove the colon, if present:
		if(labelValue && labelValue.charAt(labelValue.length-1) == ':') {
			labelValue = labelValue.substr(0, labelValue.length-1);
		}
		// If no label could be found, use the name or the id with the first character in upper case:
		if(!labelValue) {
			labelValue = element.name;
			if(!labelValue) {
				labelValue = element.id;
			}
			if(labelValue) {
				labelValue = labelValue.charAt(0).toUpperCase() + labelValue.substr(1);
			}
		}
		return labelValue;
	},
	
	getRegExpStrForValue: function(value) {
		try {
			// Remove unsave characters, escape regexp characters and return the regexp string:
			return this.getRegExpStr('(?:^'+this.escapeRegExp(this.makeSafe(value))+'$)');
		} catch(e) {
			// If an error occurs, return the safe value string.
			// If using it as regular expression fails a simple string comparison is used:
			return this.makeSafe(value);
		}
	},
	
	getFieldRuleForElement: function(element) {
		var name = element.name;
		// If no name is available use the label as fallback:
		if(!name) {
			name = this.getLabelForElement(element);
		}
		// If no name and no label is available use the id as fallback:
		if(!name) {
			name = element.id;
		}
		try {
			// Remove unsave characters, escape regexp characters and return the regexp string:
			return this.getRegExpStr('(?:^'+this.escapeRegExp(this.makeSafe(name))+'$)');
		} catch(e) {
			// If an error occurs, return an always matching regexp string:
			return '(?:)';
		}
	},
	
	getSiteRuleForURL: function(url) {
		try {
			// Remove unsave characters, escape regexp characters and return the regexp string:
			return this.getRegExpStr('(?:^'+this.escapeRegExp(this.makeSafe(url))+')');
		} catch(e) {
			// If an error occurs, return an always matching regexp string:
			return '(?:)';
		}
	},
	
	parseOptionsWindowParams: function() {
		// Check the windows arguments:
		if(window.arguments && window.arguments[0]) {
			if(window.arguments[0].targetFormField) {
				var formFieldObject = window.arguments[0].targetFormField;
				
				var value;
				switch(formFieldObject.type) {
					case 'checkbox':
					case 'radio':
					case 'select-one':
					case 'select-multiple':
						value = this.getRegExpStrForValue(formFieldObject.value);
						break;
					default:
						value = this.replaceControlCharacters(formFieldObject.value);
						break;
				}
				
				var location = this.getDoc().location;
				
				// Reset the targetFormField of the autofillForms object referenced by window.arguments[0]:
				window.arguments[0].targetFormField = null;
							
				// Set the textbox values using the form field properties and the current document location:
				document.getElementById('fieldRuleNameTextBox').value
					= this.getFieldRuleNameForElement(formFieldObject)
					+ (location.hostname ? ' - ' + location.hostname : '');
				document.getElementById('fieldRuleValueTextBox').value = value;
				document.getElementById('fieldRuleFieldRuleTextBox').value
					= this.getFieldRuleForElement(formFieldObject);
				document.getElementById('fieldRuleSiteRuleTextBox').value
					= this.getSiteRuleForURL(location.protocol + '//' + location.host);
				
				// Make sure the main pane is selected:
				document.getElementById('autofillFormsPrefs').showPane(
					document.getElementById('autofillFormsPrefPaneMain')
				);
				
				// Set the focus to the name field:
				document.getElementById('fieldRuleNameTextBox').focus();
			} else if(window.arguments[0].newProfileFromForm) {
				// Make sure the main pane is selected:
				document.getElementById('autofillFormsPrefs').showPane(
					document.getElementById('autofillFormsPrefPaneMain')
				);
			}
		}
	},
	
	restoreProfiles: function() {
		// EZ restoring the profiles
		this.myLog("Restoring the profiles");
		this.restoreFieldRules();
		this.restoreProfileLabels();
		this.restoreProfileSiteRules();

		// Update the fieldRules:
		this.setFieldRules();

		// EZ FIXME. getProfileLabels() doesn't return the new list of profiles,
		// missing a step in the restore functions above!

		// Save the profileLabels list in the preferences:
		this.setProfileLabels(this.getProfileLabels());
		// Save the profileSiteRules in the preferences:
		this.setProfileSiteRules(this.getProfileSiteRules());				
		// Save the the new profile index as selected profileIndex:
		this.setProfileIndex(this.getProfileLabels().length-1);

		// Update the profiles lists:
		this.initProfilesLists();
		// Re-init the fieldRules tree:
		this.initTree();
		// Re-initialize the simple interface:
		this.initSimpleInterface();
	},
	isProfilesChanged: function () {
		// Compare current profiles with old profiles. Any changes?
		var changed;
		if (this.cachedFieldRules != this.backupFieldRules() ||
				this.cachedProfileLabels != this.backupProfileLabels() ||
				this.cachedProfileLabels != this.backupProfileLabels()) {
			changed = true;
		} else {
			changed = false;
		}

		return changed;
	},
	backupProfiles: function() {
		// Backing-up the profiles
		this.cachedFieldRules = this.backupFieldRules();
		this.cachedProfileLabels = this.backupProfileLabels();
		this.cachedProfileSiteRules = this.backupProfileSiteRules();
	},

	optionsCancel: function() {
		// Is instant apply on (Linux)? In which case, 'Close' means 'Apply'
		if (this.getPrefManager().getBoolPref("browser.preferences.instantApply")) {
			this.myLog("Pressed Close...");
			this.flagCancelDialog = false;
		} else {
			this.myLog("Pressed Cancel...");
			this.flagCancelDialog = true;
		}
	},

	optionsAccept: function() {
		this.myLog("Pressed Accept...");
		this.flagCancelDialog = false;
	},

	optionsConfirmClose: function() {
		// This confirms that we want to close the dialog (saved or unsaved data)
		var confirmFlag = true;

		if (this.isProfilesChanged() && this.flagCancelDialog) {
			this.myLog("Pressed Cancel but profiles have changed. Please confirm Close...");

			var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
					.getService(Components.interfaces.nsIPromptService);

			var flags = promptService.BUTTON_TITLE_SAVE * promptService.BUTTON_POS_0 +
					//promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1 +
					promptService.BUTTON_TITLE_DONT_SAVE * promptService.BUTTON_POS_1;

			var result = promptService.confirmEx(window, 
					this.getStringBundle().getString('saveProfilesTitle'),
					this.getStringBundle().getString('saveProfilesText'),
					flags, null, null, null, null, {});

			switch (result) {
				case 0:
					// Pressed Save
					this.myLog("On the confirm dialog, pressed saved, saving now...");
					break;
				default:
					// Pressed don't save
					this.myLog("On the confirm dialog, pressed don't save, aka restoring profiles...");
					this.restoreProfiles();
			}
		}

		this.flagCancelDialog = false;
		return confirmFlag;
	},
	optionsFinalize: function() {
		// EZ This is where we restore the profiles if cancel was pressed.
		this.myLog("we are finalizing now...");

		// Text now displays "Do you want to save your changes..."
		// Pressing no will restore the profiles.
	},
	
	showProfileSwitcher: function() {
		if(this.autofillFormsPrefs.getBoolPref('useConfigDirectory')) {
			// Always retrieve the profile labels from file if useConfigDirectory is enabled:
			this.profileLabels = null;
		}
		// The nsIPromptService select() method doesn't offer to set a preselection,
		// so we switch the current profile label with the first item (which is selected by default):
		var list;
		var currentIndex = this.getProfileIndex();
		if(currentIndex != 0) {
			// Copy the profilLabels array (so we don't change the original):
			list = new Array().concat(this.getProfileLabels());
			// Switch the current profile label with the first item:
			var tmp = list[0];
			list[0] = list[currentIndex];
			list[currentIndex] = tmp;
		} else {
			// Set the list to the profilLabels reference if it is not to be changed:
			list = this.getProfileLabels();
		}
		var selected = {};
		// Show the selection prompt:
		var ok = this.getPrompts().select(
			window,
			this.getStringBundle().getString('profileSelectionWindowTitle'),
			this.getStringBundle().getString('profileSelectionPrompt'),
			list.length,
			list,
			selected
		);
		if(ok) {
			// If nothing changed, return:
			if(selected.value == 0)
				return;
			// If the currentIndex has been selected and is not 0, it is in fact the index 0:
			if(currentIndex != 0 && selected.value == currentIndex)
				selected.value = 0;
			// Set the profile index to the selected one:
			this.setProfileIndex(selected.value)
		}
	},

	showDialog: function(url, params) {
		var found, win, loc;

		var enumerator = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
				   .getService(Components.interfaces.nsIWindowWatcher)
				   .getWindowEnumerator(null);

		//Check whether a window with the chrome url is already open
		found = false;
		while (enumerator.hasMoreElements()) {
			win = enumerator.getNext();
			loc = new String(win.location);
			if (loc.toLowerCase() == url.toLowerCase()) {
				found = true;
				break;
			}
		}
		
		if (found == true)
		{
			// Is the window already open? Then only need to focus the window
			win.focus();
		}
		else
		{
			var paramObject = params ? params : this;
			win = window.openDialog(
				url,
				'',
				'chrome=yes,resizable=yes,toolbar=yes,centerscreen=yes,modal=no,dependent=no,dialog=no',
				paramObject
			);
		}
		return win;
	},

	inArray: function(array, item) {
		var i = array.length;
		while(i--)
			if(array[i] === item)
				return true;
		return false;
	},
	
	displayFormDetails: function() {
		this.searchAndDisplayFormDetails(this.getWin());
	},

	searchAndDisplayFormDetails: function(win) {
		win = win ? win : this.getWin();
		
		var doc = this.getDoc(win);
		
		// Check if any web forms are available on the current window:
		if(doc && doc.forms && doc.forms.length > 0) {
		
		 	// Go through the forms:
		 	for(var i = 0; i < doc.forms.length; i++) {

				// The form elements list:
				var elements = doc.forms[i].elements;
				
				// Go through the form elements:
				for(var j = 0; j < elements.length; j++) {
					this.displayFormElementDetails(elements[j], j, i, doc);
				}
			}
		}

		// Recursive call for all subframes:
		for(var f=0; f < win.frames.length; f++) {
			this.searchAndDisplayFormDetails(win.frames[f]);
		}
	},

	isValidFormField: function(element) {
		// ignore disabled (and return false) only if 'ignore disabled fields' is ticked
		if(element.disabled && this.autofillFormsPrefs.getBoolPref('ignoreDisabledFields')) {
			return false;
		}
		if(!arguments.callee.regExpFormFieldType) {
			arguments.callee.regExpFormFieldType = new RegExp(
				this.autofillFormsPrefs.getCharPref('regExpFormFieldTypes')
			);
		}

		if (!element.type)
			return false;

		return arguments.callee.regExpFormFieldType.test(element.type);
	},
	
	displayFormElementDetails: function(element, elementNumber, formNumber, doc) {
		// Create a unique id for the form element:
		var id = 'autofillForms-f' + formNumber + '-e' + elementNumber;
		
		// Remove the form details node if already present
		// (nodeType 1 is an element node):
		if(element.nextSibling && element.nextSibling.nodeType == 1 && element.nextSibling.getAttribute('id') == id) {
			element.parentNode.removeChild(element.nextSibling);
			return;
		}
		
		// Only display valid form fields:						
		if(this.isValidFormField(element)) {
			// Create a "span" node with element details:
			var text;
			// Display the element name if available, else the element id if available, else an empty name:
			if(element.name || !element.id) {
				text = 'name="' + element.name;
			} else {
				text = 'id="' + element.id;
			}
			// Display the element value:
			text += '" value="' +  element.value + '"';
			var span = doc.createElement('span');
			span.setAttribute('id', id);
			span.setAttribute('style', this.autofillFormsPrefs.getCharPref('formDetailsStyle'));
			span.setAttribute('title', text);
			span.appendChild(doc.createTextNode(text));
		
			// Insert the form details node after the element:
			if(element.nextSibling)
				element.parentNode.insertBefore(span, element.nextSibling);
			else
				element.parentNode.appendChild(span);
		}
	},
	
	ruleEditorInitialize: function() {
		// Save the reference to the Autofill Forms preferences branch:
		this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
		
		if(window.arguments && window.arguments[0] && window.arguments[0].attributes) {
			this.currentRuleField = window.arguments[0];
		}
		
		// Initialize the ruleElementTypes:
		this.ruleElementTypes = new Array();
		this.ruleElementTypes.push('contains');
		this.ruleElementTypes.push('beginsWith');
		this.ruleElementTypes.push('endsWith');
		this.ruleElementTypes.push('equals');
		
		// If the rule editor is used to edit the site rule, add two predefined protocol rules:
		if(this.currentRuleField && this.currentRuleField.id && this.currentRuleField.id.indexOf('SiteRule') != -1) {
			this.ruleEditorAdd('beginsWith', 'http:\/\/');
			this.ruleEditorAdd('beginsWith', 'https:\/\/');
		} else {
			this.ruleEditorAdd();
		}
	},
	
	ruleEditorSave: function() {
		if(document.getElementById('ruleElementsList')) {
			var str = '';
			var richlistbox = document.getElementById('ruleElementsList');
			var richlistitems = richlistbox.getElementsByTagName('richlistitem');
			var menulists,textboxes;
			
			// Go through the richlistbox items:
			for(var i=0; i<richlistitems.length; i++) {
				// Link the conditions as disjunctions (OR-Relations);
				if(str.length != 0)
					str += '|';
				menulists = richlistitems[i].getElementsByTagName('menulist');
				textboxes = richlistitems[i].getElementsByTagName('textbox');
				
				// Add the current condition to the string:
				switch(menulists[0].selectedItem.value) {
					case 'contains':
						str += '(?:' + textboxes[0].value + ')';
						break;
					case 'beginsWith':
						str += '(?:^' + textboxes[0].value + ')';
						break;
					case 'endsWith':
						str += '(?:' + textboxes[0].value + '$)';
						break;
					case 'equals':
						str += '(?:^' + textboxes[0].value + '$)';
						break;
				}
			}
			if(this.currentRuleField) {
				// Set the current field value to the created string:
				this.currentRuleField.value = str;
				// Call the onchange handler:
				if(this.currentRuleField.onchange) {
					this.currentRuleField.onchange();
				}
				// Call the focus handler:
				if(this.currentRuleField.focus) {
					this.currentRuleField.focus();
				}
			}
		}
		return true;
	},
	
	ruleEditorAdd: function(type, ruleElement) {
		if(document.getElementById('ruleElementsList')) {
			var richlistbox = document.getElementById('ruleElementsList');
			
			var richlistitem,menulist,menupopup,menuitem,textbox,label;
			
			richlistitem = document.createElement('richlistitem');
			
			// Create the condition type menu:
			menulist = document.createElement('menulist');
			menupopup = document.createElement('menupopup');
			
			var selectedIndex = 0;
			
			// Create the menu of ruleElementTypes:
			for(var i=0; i<this.ruleElementTypes.length; i++) {
				menuitem = document.createElement('menuitem');
				menuitem.setAttribute(
					'value',
					this.ruleElementTypes[i]
				);
				menuitem.setAttribute(
					'label',
					this.getStringBundle().getString(this.ruleElementTypes[i] + 'RuleType')
				);
				menupopup.appendChild(menuitem);
				
				// Set the selectedIndex:
				if(type != null && type == this.ruleElementTypes[i])
					selectedIndex = i;
			}
			
			menulist.appendChild(menupopup);
			richlistitem.appendChild(menulist);
			
			// Create the textbox:		
			textbox = document.createElement('textbox');
			if(ruleElement != null)
				textbox.setAttribute('value',ruleElement);
			textbox.setAttribute('flex','1');
			richlistitem.appendChild(textbox);

			richlistbox.appendChild(richlistitem);
			
			// Select the menuitem:
			menulist.selectedIndex = selectedIndex;
		}
	},
	
	ruleEditorRemove: function(index) {
		var ruleElementsList = document.getElementById('ruleElementsList');
		if(ruleElementsList) {
			if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
				// Confirmation dialog:
				if(!this.getPrompts().confirm(
						null,
						this.getStringBundle().getString('removeRuleConditionTitle'),
						this.getStringBundle().getString('removeRuleConditionText')
					)
				) {
					return;	
				}
			}
		
			var richlistbox = ruleElementsList;
			if(index)
				richlistbox.selectedIndex = index;
			if(richlistbox.selectedItem && richlistbox.selectedIndex != -1)
				richlistbox.removeChild(richlistbox.selectedItem);
		}
	},

	ruleEditorIsTextBoxFocused: function() {
		return this.ruleEditorTextBoxFocused;
	},
	
	ruleEditorFocus: function() {
		var focusedElement = document.commandDispatcher.focusedElement;
		
		// Monitor if a textbox is focused:
		if(!this.ruleEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'html:input') {
			this.ruleEditorTextBoxFocused = true;
		} else if(this.ruleEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'richlistbox') {
			this.ruleEditorTextBoxFocused = false;
		}
	},
	
	ruleEditorHandleKeyPress: function(event) {
		// Only remove a dynamic tag on delete key press if no textbox is focused:
		if(event.keyCode == 46 && !this.ruleEditorIsTextBoxFocused()) {
			this.ruleEditorRemove();
		}
	},

	ruleEditorFinalize: function() {
		this.currentRuleField = null;
	},

	tagEditorInitialize: function() {
		// Save the reference to the Autofill Forms preferences branch:
		this.autofillFormsPrefs = this.getPrefManager().getBranch('extensions.autofillForms@blueimp.net.');
		
		// Add existing tags to the list:
		for(var i=0; i<this.getDynamicTags().length; i++) {
			// Catch if the number of tags doesn't match the number of tag codes:
			try {
				this.tagEditorAdd(this.getDynamicTags()[i],this.getDynamicTagCodes()[i])
			} catch(e) {
				this.log(e);
			}
		}
	},
	
	tagEditorSave: function() {
		var richlistbox = document.getElementById('tagList');
		if(richlistbox) {
			var richlistitems = richlistbox.getElementsByTagName('richlistitem');
			var textboxes;
			
			var dynamicTags = new Array();
			var dynamicTagCodes = new Array();
			
			// Go through the richlistbox items:
			for(var i=0; i<richlistitems.length; i++) {
				textboxes = richlistitems[i].getElementsByTagName('textbox');
				
				// Add the dynamic tags and their associated tag codes to the lists:
				if (textboxes[0].value != '' && textboxes[1].value != '') {
					dynamicTags.push(this.makeSafe(textboxes[0].value));
					dynamicTagCodes.push(this.makeSafe(textboxes[1].value));
				}
			}
			// Save the lists in the preferences:
			this.setDynamicTags(dynamicTags);
			this.setDynamicTagCodes(dynamicTagCodes);
		}
		return true;
	},
	
	tagEditorAdd: function(tag, tagCode) {
		var richlistbox = document.getElementById('tagList');
		if(richlistbox) {			
			var richlistitem,textbox;
			
			richlistitem = document.createElement('richlistitem');

			// Create the tag textbox:		
			textbox = document.createElement('textbox');
			textbox.setAttribute('class','tag');
			if(tag != null)
				textbox.setAttribute('value',tag);
			richlistitem.appendChild(textbox);

			// Create the tagCode textbox:		
			textbox = document.createElement('textbox');
			textbox.setAttribute('class','tagCode');
			textbox.setAttribute('flex','1');
			if(tagCode != null)
				textbox.setAttribute('value',tagCode);
			richlistitem.appendChild(textbox);
			
			richlistbox.appendChild(richlistitem);
		}
	},
	
	tagEditorRemove: function(index) {
		var richlistbox = document.getElementById('tagList');
		if(richlistbox) {
			if(this.autofillFormsPrefs.getBoolPref('enableConfirmationDialogs')) {
				// Confirmation dialog:
				if(!this.getPrompts().confirm(
						null,
						this.getStringBundle().getString('removeDynamicTagTitle'),
						this.getStringBundle().getString('removeDynamicTagText')
					)
				) {
					return;	
				}
			}
			if(index)
				richlistbox.selectedIndex = index;
			if(richlistbox.selectedItem && richlistbox.selectedIndex != -1)
			{
				richlistbox.removeChild(richlistbox.selectedItem);
			}
		}
	},

	tagEditorValidate: function(index) {
		var richlistbox = document.getElementById('tagList');
		if(richlistbox) {
			if(index)
				richlistbox.selectedIndex = index;
			if(richlistbox.selectedItem) {
				var tagCode = richlistbox.selectedItem.lastChild.value;
				var validationResultTextBox = document.getElementById('validationResultTextBox');
				try {
					validationResultTextBox.removeAttribute('style');
					// We use eval() here without restrictions - the given tagCode must be trusted:
					validationResultTextBox.value = eval(tagCode);
				} catch(e) {
					validationResultTextBox.setAttribute('style', 'color:red;');
					validationResultTextBox.value = e;
				}
			}
		}
	},

	tagEditorIsTextBoxFocused: function() {
		return this.tagEditorTextBoxFocused;
	},
	
	tagEditorFocus: function() {
		var focusedElement = document.commandDispatcher.focusedElement;
		
		// Monitor if a textbox is focused:
		if(!this.tagEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'html:input') {
			this.tagEditorTextBoxFocused = true;
		} else if(this.tagEditorTextBoxFocused && focusedElement && focusedElement.tagName == 'richlistbox') {
			this.tagEditorTextBoxFocused = false;
		}
	},
	
	tagEditorHandleKeyPress: function(event) {
		// Only remove a dynamic tag on delete key press if no textbox is focused:
		if(event.keyCode == 46 && !this.tagEditorIsTextBoxFocused()) {
			this.tagEditorRemove();
		}
	},
	
	tagEditorFinalize: function() {
	},

	openHelp: function(topic) {
		if(!topic) {
			topic = '';
		}
alert(this.pascalCase(topic));
		var url = this.autofillFormsPrefs.getCharPref('helpUrl').replace(/\[TOPIC\]/, topic).replace(/\[PASCALCASE\]/, this.pascalCase(topic)).split('|')[this.helpUrlIndex*2+1];
		this.openNewTab(url, true);
	},
	
	openNewTab: function(url, focus) {
		var helpTab = this.getBrowser().addTab(url);
		if(focus) {
			this.getBrowser().selectedTab = helpTab;
			this.getWindowMediator().getMostRecentWindow('navigator:browser').focus();
		}
	},

	pascalCase: function(str) {
		// lifted from http://stackoverflow.com/questions/4068573/convert-string-to-pascal-case-aka-uppercamelcase-in-javascript
		// Thanks, Kobi!
		return str.replace(/(\w)(\w*)/g,
			function(g0,g1,g2){return g1.toUpperCase() + g2.toLowerCase();}).replace(' ','');
	},

	addLeadingZeros: function(number, length) {
		number = number.toString();
		while(number.length < length) {
			number = '0'+number;
		}
		return number;
	},
	
	escapeRegExp: function(str) {
		if (!arguments.callee.regExp) {
			var specials = new Array(
				'^', '$', '*', '+', '?', '.', '|', '/',
				'(', ')', '[', ']', '{', '}', '\\'
			);
			arguments.callee.regExp = new RegExp(
				'(\\' + specials.join('|\\') + ')', 'g'
			);
		}
		return str.replace(arguments.callee.regExp, '\\$1');
	},
	
	getClipboardText: function() {
		var clipboardText = null;
		var clip  = Components.classes['@mozilla.org/widget/clipboard;1']
						.getService(Components.interfaces.nsIClipboard);
		if(!clip) {
			return null;
		}
		
		var trans = Components.classes['@mozilla.org/widget/transferable;1']
						.createInstance(Components.interfaces.nsITransferable);
		if(!trans) {
			return null;
		}
		
		trans.addDataFlavor('text/unicode');
		
		clip.getData(trans, clip.kGlobalClipboard);
		
		var str	   = new Object();
		var strLength = new Object();
		
		trans.getTransferData('text/unicode', str, strLength);
		
		if(str) {
			str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
		}
		if(str) {
			clipboardText = str.data.substring(0, strLength.value / 2);
		}
		
		return clipboardText
	},

	getMasterSecurityDevice: function() {
		return Components.classes['@mozilla.org/security/pk11tokendb;1']
				.getService(Components.interfaces.nsIPK11TokenDB);
	},
	
	log: function(aMessage, aSourceName, aSourceLine, aLineNumber, aColumnNumber, aFlags, aCategory) {
		var consoleService = Components.classes['@mozilla.org/consoleservice;1']
			.getService(Components.interfaces.nsIConsoleService);
		if(aSourceName != 'undefined') {
			var scriptError = Components.classes["@mozilla.org/scripterror;1"]
				.createInstance(Components.interfaces.nsIScriptError);
			scriptError.init(
				aMessage,
				aSourceName,
				aSourceLine,
				aLineNumber,
				aColumnNumber,
				aFlags,
				aCategory
			);
			consoleService.logMessage(scriptError);
		} else {
			consoleService.logStringMessage(aMessage);	
		}
	},

	finalizeToolbarButtonStatus: function() {
		var autofillFormsButton = document.getElementById('autofillFormsButton');
		var hideToolbarButton = this.autofillFormsPrefs.getBoolPref('hideToolbarButton');
		if(!autofillFormsButton && !hideToolbarButton) {
			// If the toolbar button icon has been removed from the toolbar by drag&drop
			// enable the hideToolbarButton setting:
			this.autofillFormsPrefs.setBoolPref('hideToolbarButton', true);
		} else if(autofillFormsButton && !autofillFormsButton.getAttribute('hidden')) {
			// If the toolbar button icon has been added to the toolbar by drag&drop
			// disable the hideToolbarButton setting:
			this.autofillFormsPrefs.setBoolPref('hideToolbarButton', false);
		}
	},
	
	finalize: function() {
		this.finalizeToolbarButtonStatus();
		
		// Remove the content area context menu listener:
		var contentAreaContextMenu = document.getElementById('contentAreaContextMenu');
		if(contentAreaContextMenu) {
			contentAreaContextMenu.removeEventListener(
				'popupshowing',
				this.contentAreaContextMenuEventListener,
				false
			);
		}
		
		// Remove the preferences Observer:
		this.autofillFormsPrefs.removeObserver('', this);
	}
	
}
